﻿<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<title>第24章. 性能</title>
<link rel="stylesheet" href="css/espertech.css" type="text/css">
	<meta name="generator" content="DocBook XSL-NS样式表V1.74.0">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
			<link rel="home" href="index.html" title="Esper参考手册">
				<link rel="up" href="index.html" title="Esper参考手册">
					<link rel="prev" href="examples.html" title="第23章. 示例、教程、案例研究">
						<link rel="next" href="references.html" title="第25章. 参考文献">
</head>

<body>
	<p id="title">
		<a href="./index.html" class="site_href"><strong>www.espertech.com</strong></a><a href="http://www.espertech.com/esper/esper-documentation/" class="doc_href"><strong>文档</strong></a>
	</p>
	<ul class="docnav">
		<li class="previous"><a accesskey="p" href="examples.html"><strong>上一页</strong></a></li>
		<li class="next"><a accesskey="n" href="references.html"><strong>下一页</strong></a></li>
	</ul>
	<div class="chapter" lang="en-US">
		<div class="titlepage">
			<div>
				<div>
					<h2 class="title"><a id="performance"></a>第24章. 性能</h2>
				</div>
			</div>
		</div>
		<div class="toc">
			<dl>
				<dt><span class="sect1"><a href="performance.html#performance-bigo">24.1. 大O表示法</a></span></dt>
				<dd>
					<dl>
						<dt><span class="sect2"><a href="performance.html#performance-bigo-filterindex">24.1.1. 将事件匹配到语句和上下文分区的大O复杂度</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#performance-bigo-time">24.1.2. 将时间匹配到语句和上下文分区的大O复杂度</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#performance-bigo-select">24.1.3. 连接、子查询、On-Select、On-Merge、On-Update、On-Delete的大O复杂度</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#performance-bigo-enummethod">24.1.4. 枚举方法的大O复杂度</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#performance-bigo-aggmethod">24.1.5. 聚合方法的大O复杂度</a></span></dt>
					</dl>
				</dd>
				<dt><span class="sect1"><a href="performance.html#performance-tips">24.2. 性能提示</a></span></dt>
				<dd>
					<dl>
						<dt><span class="sect2"><a href="performance.html#perf-tips-1">24.2.1. 了解如何调整Java虚拟机</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-3">24.2.2. 输入和输出瓶颈</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-3-a">24.2.3. 线程</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-4">24.2.4. 选择基础事件而不是单个字段</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-5">24.2.5. 优先使用流级过滤而不是Where子句过滤</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-6">24.2.6. 减少表达式中算术运算的使用</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-6a">24.2.7. 删除不必要的构造</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-6b">24.2.8. 结束模式子表达式</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-7">24.2.9. 考虑使用EventPropertyGetter快速访问事件属性</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-8">24.2.10. 考虑转换基础事件</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-9">24.2.11. 关闭日志记录和审计</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-11">24.2.12. 调整或禁用交付顺序保证</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-12">24.2.13. 使用订阅者对象接收事件</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-12b">24.2.14. 考虑数据流</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13">24.2.15. 高到达率流和单个语句</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13a">24.2.16. 子查询与连接、Where子句和数据窗口</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13b">24.2.17. 模式和模式子表达式实例</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13b2">24.2.18. 模式子表达式实例与数据窗口使用</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13c">24.2.19. 全保留数据窗口</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-13d">24.2.20. 减少内存消耗的语句设计 - 诊断OutOfMemoryError</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-14">24.2.21. 性能、JVM、操作系统和硬件</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-15">24.2.22. 考虑使用提示</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-16">24.2.23. 优化流过滤表达式</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-17">24.2.24. 语句和运行时指标报告</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-18">24.2.25. 表达式评估顺序和提前退出</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-19">24.2.26. 大量线程</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-19a">24.2.27. 过滤评估调整</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-20">24.2.28. 上下文分区相关信息</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-21">24.2.29. 优先使用常量变量而非非常量变量</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-22">24.2.30. 优先使用POJO事件或对象数组事件</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-24">24.2.31. 查询规划注意事项</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-25a">24.2.32. 查询规划表达式分析提示</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-25b">24.2.33. 查询规划索引提示</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-26">24.2.34. 测量吞吐量</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-27">24.2.35. 不要X次创建相同或相似的语句</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-28">24.2.36. 比较单线程和多线程性能</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-29">24.2.37. 命名窗口事件的增量聚合与重新计算聚合</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-30">24.2.38. 内存何时释放</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-31">24.2.39. 测量不匹配情况以及观察的吞吐量</a></span></dt>
						<dt><span class="sect2"><a href="performance.html#perf-tips-32">24.2.40. 当事件类型具有大量事件属性即大事件时的选项</a></span></dt>
					</dl>
				</dd>
				<dt><span class="sect1"><a href="performance.html#performance-kit">24.3. 使用性能工具包</a></span></dt>
				<dd>
					<dl>
						<dt><span class="sect2"><a href="performance.html#how-to-kit">24.3.1. 如何使用性能工具包</a></span></dt>
					</dl>
				</dd>
			</dl>
		</div>

		<div class="sect1" lang="en-US">
			<div class="titlepage">
				<div>
					<div>
						<h2 class="title"><a id="performance-bigo"></a>24.1. 大O表示法</h2>
					</div>
				</div>
			</div>
			<p>大O表示法用于根据算法的运行时间随输入规模增长的方式对算法进行分类。 本章讨论由EPL运行时实现的算法的大O复杂度。</p>
			<p>
				对于哈希查找，O表示法的值是近似值。对于对数运行时间函数，我们使用O(log N)，但实际指的是O(log<sub>t</sub> N)，其中 <span class="emphasis"><em>t</em></span> 为未指定的值。
			</p>
			<div class="note">
				<h2>注意</h2>
				<p>这里提供的大O表示法是一般性的指导。本节内容可能并不完全完整，也可能没有列出所有的特殊情况。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="performance-bigo-filterindex"></a>24.1.1. 将事件匹配到语句和上下文分区的大O复杂度</h3>
						</div>
					</div>
				</div>
				<p>
					<span class="strong"><strong>运行时会为每个事件确定</strong></span> 哪些EPL语句必须处理该事件。 对于使用多个分区进行上下文分区的EPL语句，例如重叠、键控、哈希或类别上下文（请参阅 <a class="xref" href="context.html" title="第4章. 上下文和上下文分区">第4章，《上下文和上下文分区》</a>）， 运行时会为每个事件确定每个语句的哪些分区必须处理该事件。
				</p>
				<p>
					此操作 <span class="strong"><strong>在以下情况发生：</strong></span>
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>
								当应用程序调用 <code class="literal">EPEventService</code> 的 <code class="literal">sendEvent</code> 方法时。
							</p>
							<p>
								例如，应用程序调用 <code class="literal">eventService.sendEventBean(new StockTickEvent(...), "StockTick")</code>，运行时会确定哪些EPL语句和分区必须处理 <code class="literal">StockTick</code> 事件。
							</p>
						</li>
						<li>
							<p>
								当运行时计算使用 <code class="literal">insert into</code> 的语句时。
							</p>
							<p>
								例如，应用程序创建一个语句 <code class="literal">insert into StockTickOverPrice100 select * as price from StockTick(price&gt;100)</code>。 在处理了一个价格大于100的 <code class="literal">StockTick</code> 事件后，运行时会分配一个新的 <code class="literal">StockTickOverPrice100</code> 事件，并确定哪些EPL语句和分区必须处理这个新的 <code class="literal">StockTickOverPrice100</code> 事件。
							</p>
						</li>
					</ul>
				</div>
				<p>
					<span class="strong"><strong>该操作的参数</strong></span> 是单个事件，例如 <code class="literal">StockTick</code> 或 <code class="literal">StockTickOverPrice100</code> 事件。
				</p>
				<p>
					<span class="strong"><strong>数据结构</strong></span> 是过滤索引，这是一种按事件类型组织的可嵌套索引树，请参阅 <a class="xref" href="processingmodel.html#processingmodel_indexes_filterindexes" title="2.18.2. 过滤索引">2.18.2节，“过滤索引”</a>。
				</p>
				<p>
					<span class="strong"><strong>算法</strong></span> 会获取单个事件，并遍历过滤索引，以确定哪些语句和上下文分区必须处理该事件。
				</p>
				<p>
					<span class="strong"><strong>输入</strong></span> 是EPL语句的过滤表达式的存在情况和性质，以及分区的数量，包括where子句（有关where子句的重写，请参阅 <a class="xref" href="epl_clauses.html#epl-where-clause" title="5.5. 指定搜索条件：Where子句">5.5节，“指定搜索条件：Where子句”</a>）。
				</p>
				<p>
					<span class="strong"><strong>大O复杂度</strong></span> 取决于输入。它总是包括按事件类型进行的哈希查找，这是常数时间 O(1)。
				</p>
				<div class="sect4" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h5 class="title"><a id="performance-bigo-filterindex-ex1"></a>24.1.1.1. 示例1</h5>
							</div>
						</div>
					</div>
					<p>假设输入是如下的N条语句。</p>
					<pre class="synopsis">select * from Event(property=<span class="emphasis"><em>value</em></span>)</pre>
					<p>
						复杂度是常数时间 O(1)，因为相同的属性出现在所有过滤表达式中，并且使用了等于运算符 (<code class="literal">=</code>)，因此是哈希查找。
					</p>
					<p>例如在这个EPL中：</p>
					<pre class="synopsis">select * from StockTick(symbol='A');
select * from StockTick(symbol='B');</pre>
					<p>运行时会获取一次股票行情事件的股票代码值，并执行一次哈希查找。</p>
				</div>
				<div class="sect4" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h5 class="title"><a id="performance-bigo-filterindex-ex2"></a>24.1.1.2. 示例2</h5>
							</div>
						</div>
					</div>
					<p>假设输入是如下的N条语句，其中每个属性名都是不同的事件属性名：</p>
					<pre class="synopsis">select * from Event(<span class="emphasis"><em>property_n</em></span>=<span class="emphasis"><em>value</em></span>)</pre>
					<p>复杂度是线性时间 O(N)，因为不同的属性出现在所有过滤表达式中。</p>
					<p>例如在这个EPL中：</p>
					<pre class="synopsis">select * from StockTick(symbol='A');
select * from StockTick(feed='001');</pre>
				</div>
				<div class="sect4" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h5 class="title"><a id="performance-bigo-filterindex-ex3"></a>24.1.1.3. 示例3</h5>
							</div>
						</div>
					</div>
					<p>假设输入是如下的N条语句。</p>
					<pre class="synopsis">select * from Event(property_1=<span class="emphasis"><em>constant</em></span> and property_2&gt;<span class="emphasis"><em>value</em></span>)</pre>
					<p>
						复杂度是 O(log N)。相同的属性名出现在所有过滤表达式中。关系大于运算符 (<code class="literal">&gt;</code>) 是一个B树查找。
					</p>
					<p>例如在这个EPL中：</p>
					<pre class="synopsis">select * from StockTick(symbol='A', price&gt;100);
select * from StockTick(symbol='A', price&gt;200);</pre>
					<p>运行时会获取一次股票行情事件的股票代码值，并执行一次哈希查找。 它会获取一次股票行情事件的价格值，并执行一次B树查找。</p>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="performance-bigo-time"></a>24.1.2. 将时间匹配到语句和上下文分区的大O复杂度</h3>
						</div>
					</div>
				</div>
				<p>
					<span class="strong"><strong>当时间推进时，运行时会确定</strong></span> 哪些EPL语句必须处理新的运行时时间。 对于使用多个分区进行上下文分区的EPL语句，例如重叠、键控、哈希或类别上下文（请参阅 <a class="xref" href="context.html" title="第4章. 上下文和上下文分区">第4章，《上下文和上下文分区》</a>）， 运行时会确定每个语句的哪些分区必须处理新的运行时时间。
				</p>
				<p>
					此操作 <span class="strong"><strong>在以下情况发生：</strong></span>
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>
								当应用程序调用 <code class="literal">EPEventService</code> 的 <code class="literal">advanceTime</code> 或 <code class="literal">advanceTimeSpan</code> 方法时（使用外部计时器时）。
							</p>
							<p>
								例如，应用程序调用 <code class="literal">eventService.advanceTime(DateTime.parse("2002-05-30T09:01:02.003"))</code>，运行时会确定哪些EPL语句和分区必须处理新的运行时时间。
							</p>
						</li>
						<li>
							<p>当运行时使用内部计时器（即系统时间）并且当前系统时间变为当前运行时时间时。</p>
						</li>
					</ul>
				</div>
				<p>
					<span class="strong"><strong>该操作的参数</strong></span> 是新的运行时时间。
				</p>
				<p>
					<span class="strong"><strong>数据结构</strong></span> 是运行时内部维护的调度表，这是一种按时间排序的数据结构。
				</p>
				<p>
					<span class="strong"><strong>算法</strong></span> 会获取新的运行时时间并进行查找。
				</p>
				<p>
					<span class="strong"><strong>输入</strong></span> 是EPL语句中与时间相关的表达式的存在情况和性质，以及分区的数量。
				</p>
				<p>
					<span class="strong"><strong>大O复杂度</strong></span> 是 O(log N)。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="performance-bigo-select"></a>24.1.3. 连接、子查询、On-Select、On-Merge、On-Update、On-Delete的大O复杂度</h3>
						</div>
					</div>
				</div>
				<p>
					<span class="strong"><strong>运行时会执行 </strong></span> 连接、子查询、on-select、on-merge、on-update、on-delete 或即发即弃查询。 运行时通过执行索引查找来确定事件的子集（或表的行）。它会对子集执行额外的操作。
				</p>
				<p>
					查询规划器负责确定要使用的索引。查询规划器使用 <code class="literal">where</code> 子句（如果有）和 <code class="literal">on</code> 子句（如果有）来规划索引的使用。 使用查询计划日志记录来获取有关查询计划的信息。
				</p>
				<p>
					此操作 <span class="strong"><strong>在以下情况发生：</strong></span>
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>当它是一个连接时，运行时会对一个流的事件（或命名窗口或表）进行查找，以解析该流（或命名窗口或表）的事件子集，从而确定最终的连接结果。</p>
						</li>
						<li>
							<p>当它是一个子查询时，运行时会对子查询的事件（或聚合行）进行查找，以解析子查询结果事件的子集并处理这些事件。</p>
						</li>
						<li>
							<p>当它是一个on-action语句，例如on-select、on-merge、on-update和on-delete时，运行时会对命名窗口的事件或表的行进行查找，以解析子集并处理这些内容。</p>
						</li>
						<li>
							<p>当它是一个即发即弃的select、update或delete查询时。</p>
						</li>
					</ul>
				</div>
				<p>
					<span class="strong"><strong>该操作的参数</strong></span> 是from子句和on-trigger的事件。对于即发即弃查询，参数源自过滤表达式和 <code class="literal">where</code> 子句。
				</p>
				<p>
					<span class="strong"><strong>数据结构</strong></span> 是事件索引，请参阅 <a class="xref" href="processingmodel.html#processingmodel_indexes_eventindexes" title="2.18.3. 事件索引">2.18.3节，“事件索引”</a>。
				</p>
				<p>
					<span class="strong"><strong>算法</strong></span> 会获取事件数据，并根据选择的索引组织执行索引查找，以确定并处理事件的子集。
				</p>
				<p>
					<span class="strong"><strong>输入</strong></span> 是已索引的事件（或表的行）。
				</p>
				<p>
					<span class="strong"><strong>大O复杂度</strong></span> 取决于查找操作的类型。
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>对于仅使用哈希查找的操作，复杂度是常数时间 O(1)。</p>
						</li>
						<li>
							<p>对于使用B树查找或结合了哈希和B树查找的操作，复杂度是 O(log N)。</p>
						</li>
						<li>
							<p>否则，复杂度是 O(N)。如果没有索引，扫描必须检查每个事件，这意味着它将随着事件数量的增加而扩展。</p>
						</li>
					</ul>
				</div>
				<div class="sect4" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h5 class="title"><a id="performance-bigo-eventindex-ex2"></a>24.1.3.2. 示例2</h5>
							</div>
						</div>
					</div>
					<p>本示例声明了一个名为`TickWindow`的命名窗口，用于保存`StockTick`事件。它使用`on-select`选择所有价格高于`PriceQuery`提供价格的股票行情：</p>
					<pre class="synopsis">create window TickWindow#time(10) from StockTick; 
on PriceQuery as priceQuery select * from TickWindow as ticks where ticks.price &gt; priceQuery.price;</pre>
					<p>`where`子句使用了关系大于运算符（`>`），查询规划器规划了一个B树索引查找。该操作的复杂度为O(log N)，其中N与`TickWindow`事件的数量相关。</p>
				</div>
				<div class="sect4" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h5 class="title"><a id="performance-bigo-eventindex-ex3"></a>24.1.3.3. 示例3</h5>
							</div>
						</div>
					</div>
					<p>本示例是一个单向连接。当一个`RFIDEvent`到达时，它会为每个唯一区域ID的`Zone`事件输出一行数据：</p>
					<pre class="synopsis">select * from RFIDEvent unidirectional, Zone#unique(zoneId)</pre>
					<p>该查询没有`where`子句，查询规划器无法使用索引。该操作的时间复杂度为线性时间O(N)，其中N与`Zone`事件的数量相关。</p>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="performance-bigo-enummethod"></a>24.1.4. 枚举方法的大O复杂度</h3>
						</div>
					</div>
				</div>
				<p>
					<span class="strong"><strong>运行时通过对集合中的每个元素应用操作来计算</strong></span> 枚举方法。
				</p>
				<p>
					此操作 <span class="strong"><strong>针对每个枚举方法发生</strong></span>。
				</p>
				<p>
					<span class="strong"><strong>该操作的参数</strong></span> 是传递给枚举方法的参数。
				</p>
				<p>
					<span class="strong"><strong>算法</strong></span> 迭代集合并执行操作。
				</p>
				<p>
					<span class="strong"><strong>输入</strong></span> 是事件或标量值的集合。
				</p>
				<p>
					<span class="strong"><strong>大O复杂度</strong></span> 取决于枚举方法。
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>对于`take`和`takeLast`，当元素数量较少且大小参数较小时，复杂度为常数时间O(1)；在最坏的情况下，当大小参数较大时，复杂度为线性时间O(N)。</p>
						</li>
						<li>
							<p>对于`countOf`，复杂度通常为常数时间O(1)，对于不提供大小的数据结构，复杂度可能为线性时间O(N)。</p>
						</li>
						<li>
							<p>对于所有其他枚举方法，复杂度为线性时间O(N)。</p>
						</li>
					</ul>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="performance-bigo-aggmethod"></a>24.1.5. 聚合方法的大O复杂度</h3>
						</div>
					</div>
				</div>
				<p>
					<span class="strong"><strong>运行时通过对聚合状态应用操作来计算</strong></span> 聚合方法。
				</p>
				<p>
					此操作 <span class="strong"><strong>针对每个聚合方法发生</strong></span>。
				</p>
				<p>
					<span class="strong"><strong>该操作的参数</strong></span> 是传递给聚合方法的参数。
				</p>
				<p>
					<span class="strong"><strong>算法</strong></span> 计算参数并查询聚合状态。
				</p>
				<p>
					<span class="strong"><strong>输入</strong></span> 是聚合状态中保存的聚合事件数量。
				</p>
				<p>
					<span class="strong"><strong>排序聚合的大O复杂度</strong></span> 如下：
				</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>对于`eventsBetween`，复杂度在小范围时为O(log N)，在最坏情况下（大范围时）可能为线性时间O(N)。</p>
						</li>
						<li>
							<p>对于排序聚合上的所有其他聚合方法，复杂度为O(log N)。</p>
						</li>
					</ul>
				</div>
				<p>
					<span class="strong"><strong>窗口聚合方法的大O复杂度</strong></span> 为常数O(1)。
				</p>
			</div>
		</div>
		<div class="sect1" lang="en-US">
			<div class="titlepage">
				<div>
					<div>
						<h2 class="title"><a id="performance-tips"></a>24.2. 性能提示</h2>
					</div>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-1"></a>24.2.1. 了解如何调整你的Java虚拟机</h3>
						</div>
					</div>
				</div>
				<p>编译器和运行时在JVM上运行，你需要熟悉JVM的调优。 需要考虑的关键参数包括最小和最大堆内存以及新生代堆大小。 带有基于时间或基于长度的数据窗口的语句可能会消耗大量内存，因为它们的大小或长度可能很大。</p>
				<p>对于基于时间的数据窗口，需要注意的是，消耗的内存取决于实际的事件流输入吞吐量。事件模式实例也会消耗内存，特别是当在模式中使用“every”关键字来重复模式子表达式时——这同样将取决于实际的事件流输入吞吐量。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-3"></a>24.2.2. 输入和输出瓶颈</h3>
						</div>
					</div>
				</div>
				<p>
					你的应用程序通过<code class="literal">UpdateListener</code>接口或通过强类型的订阅者POJO对象接收来自语句的输出事件。这些输出事件由将输入事件发送到运行时实例的应用程序或定时器线程交付。
				</p>
				<p>你的监听器或订阅者对输出事件的处理会暂时阻塞线程，直到处理完成，因此可能会降低吞吐量。因此，对于你的应用程序来说，异步处理输出事件并且在监听器处理输出事件时不阻塞运行时可能是有益的，特别是如果你的监听器代码执行阻塞IO操作。</p>
				<p>例如，你的应用程序可能希望将输出事件发送到JMS目的地或将输出事件数据写入关系数据库。为了获得最佳吞吐量，考虑在单独的线程中执行此类阻塞操作。</p>
				<p>此外，在性能测试中从存储或网络读取输入事件时，你可能会发现运行时处理事件的速度比你将事件输入到运行时的速度快。在这种情况下，你可能需要考虑在性能测试中使用内存驱动程序。还可以考虑通过使用多个读取器或从存储中预取数据来将读取操作与事件处理操作（sendEvent方法）解耦。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-3-a"></a>24.2.3. 线程</h3>
						</div>
					</div>
				</div>
				<p>我们建议使用多个线程将事件发送到运行时。下面有一个测试类。 我们的测试类不使用阻塞队列和线程池，以避免出现竞争点。</p>
				<p>提供了一个用于多线程性能测试的示例代码：</p>
				<pre class="">public class SampleClassThreading {

    public static void main(String[] args) throws InterruptedException {

        int numEvents = 1000000;
        int numThreads = 3;

        Configuration config = new Configuration();
        config.getRuntime().getThreading().setListenerDispatchPreserveOrder(false);
        config.getRuntime().getThreading().setInternalTimerEnabled(false);   // 移除处理时间推进的线程
        config.getCommon().addEventType(MyEvent.class);

        String epl = "create context MyContext coalesce by consistent_hash_crc32(id) " +
                     "from MyEvent granularity 64 preallocate;\n" +
                     "@name('result') context MyContext select count(*) from MyEvent group by id;\n";
        EPCompiled compiled;
        try {
            compiled = EPCompilerProvider.getCompiler().compile(epl, new CompilerArguments(config));
        }
        catch (EPCompileException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
                
        EPRuntime runtime = EPRuntimeProvider.getDefaultRuntime(config);
        EPDeployment deployment;
        try {
            deployment = runtime.getDeploymentService().deploy(compiled);
        }
        catch (EPDeployException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
        EPStatement stmt = runtime.getDeploymentService().getStatement(deployment.getDeploymentId(), "result");
        stmt.setSubscriber(new MySubscriber());

        Thread[] threads = new Thread[numThreads];
        CountDownLatch latch = new CountDownLatch(numThreads);

        int eventsPerThreads = numEvents / numThreads;
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(
              new MyRunnable(latch, eventsPerThreads, runtime.getEventService()));
        }
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < numThreads; i++) {
            threads[i].start();
        }

        latch.await(10, TimeUnit.MINUTES);
        if (latch.getCount() > 0) {
            throw new RuntimeException("Failed to complete in 10 minute");
        }
        long delta = System.currentTimeMillis() - startTime;
        System.out.println("Took " + delta + " millis");
    }

    public static class MySubscriber {
        public void update(Object[] args) {
        }
    }

    public static class MyRunnable implements Runnable {
        private final CountDownLatch latch;
        private final int numEvents;
        private final EPEventService eventService;

        public MyRunnable(CountDownLatch latch, int numEvents, EPEventService eventService) {
            this.latch = latch;
            this.numEvents = numEvents;
            this.eventService = eventService;
        }

        public void run() {
            Random r = new Random();
            for (int i = 0; i < numEvents; i++) {
                eventService.sendEventBean(new MyEvent(r.nextInt(512)), "MyEvent");
            }
            latch.countDown();
        }
    }

    public static class MyEvent {
        private final int id;

        public MyEvent(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }
    }
}</pre>
				<p>
					我们建议使用如上的Java线程，或者使用带有<code class="literal">sendEvent<span class="emphasis"><em>Type</em></span></code>的阻塞队列和线程池，或者如果你的应用程序尚未使用线程，我们建议配置入站线程。 运行时提供了配置选项，可用于入站、出站和内部执行的运行时级队列和线程池。有关更多信息，请参阅 <a class="xref" href="apiruntime.html#apiruntime-threading-advanced" title="16.8.1. 高级线程">16.8.1节，“高级线程”</a>。
				</p>
				<p>如果你的监听器是阻塞的，我们建议使用出站线程。有关出站线程，另请参阅下面关于调整和禁用监听器交付保证的部分。</p>
				<p>如果启用高级线程选项，请记住运行时将维护一个队列和线程池。将工作单元放入队列、维护队列以及线程之间的交接会带来额外的开销。并非所有JVM上的Java阻塞队列都一定运行得很快。 不一定使用任何高级线程选项你的应用程序就会表现得更好。</p>
				<p>
					我们发现在Linux系统上，使用<code class="literal">-server</code>运行Java，并将线程固定到独占CPU上，并且确保系统上有可用的CPU时，可扩展性会更好。
				</p>
				<p>我们建议查看LMAX Disruptor，这是一个线程间消息传递库，用于设置处理阶段。然而，据报道Disruptor不太适合设置工作线程池。</p>
				<div class="sect3" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h4 class="title"><a id="perf-tips-4-threadpoolpattern"></a>24.2.3.1. 线程池模式</h4>
							</div>
						</div>
					</div>
					<p>下面的示例代码可能有助于你开始设置带有背压的工作线程池，并考虑IO线程和干净的关闭。</p>
					<p>示例代码从设置线程工厂开始：</p>
					<pre class="">private static class RuntimeThreadFactory implements ThreadFactory {
  private AtomicInteger id = new AtomicInteger(0);

  public Thread newThread(Runnable r) {
    Thread t = new Thread(r, "Event Runtime Thread #" + id.incrementAndGet());
    t.setDaemon(true);
    t.setPriority(Thread.NORM_PRIORITY);
    return t;
  }
}</pre>
					<p>该示例使用固定大小的数组阻塞队列。为了处理队列已满且不再接受更多消息的情况， 它使用一个拒绝处理程序，该处理程序计算拒绝和重试的次数：</p>
					<pre class="">private class RuntimeRejectionHandler implements RejectedExecutionHandler {
  private volatile long spinCount = 0;
  
  public long getSpinCount() {
    return spinCount;
  }

  public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    ++spinCount;

    try {
      boolean isAccepted = false;
      while (!isAccepted) {
        isAccepted = executorQueue.offer(r, 120, TimeUnit.MICROSECONDS);
      }
    }
    catch (InterruptedException e) {
      log.warn("could not queue work entry");
    }
  }
}</pre>
					<p>提交事件进行处理的Runnable可能如下所示：</p>
					<pre class="">class Holder implements Runnable {
  public void run() {
    // 执行任何“准备”事件所需的操作，且不涉及IO
    runtime.getEventService().sendEventBean(lm, "LMEventType");
  }
}</pre>
					<p>如下初始化队列和工作线程池：</p>
					<pre class="">  private final static int CAPACITY = 10000;
  private final static int THREAD_COUNT = 4;

  private static EPRuntime runtime;
  private ThreadFactory threadFactory = new RuntimeThreadFactory();
  private RuntimeRejectionHandler rejectionHandler = new RuntimeRejectionHandler();
  private BlockingQueue<Runnable> executorQueue;
  private ThreadPoolExecutor executor;

  public void start() {
    executorQueue = new ArrayBlockingQueue<Runnable>(CAPACITY);
    executor = new ThreadPoolExecutor(THREAD_COUNT, THREAD_COUNT, 0, TimeUnit.SECONDS,
    executorQueue, threadFactory, rejectionHandler);
    executor.allowCoreThreadTimeOut(false);
    while (executor.getPoolSize() < executor.getCorePoolSize()) {
      executor.prestartCoreThread();
    }
  }
					</pre>
					<p>为了干净地关闭，并且在销毁运行时之前，示例代码如下：</p>
					<pre class="">  executor.shutdown();
  while (!executor.isTerminated()) {
    Thread.sleep(100);
  }</pre>
					<p>下一个示例代码进入IO或输入线程，例如NIO映射文件、文件通道、套接字通道或zmq / nanomsg等，并将工作单元提交到队列：</p>
					<pre class="">  while (programAlive) {
    // 将事件反序列化为POJO、Map、Array等，
    // 需要时传递事件类型名称
    executor.execute(new Holder(myeventobject));
  }</pre>
					<p>
						你可以定期转储<code class="literal">spinCount</code>变量以了解队列深度。 你可以调整Executor线程池的大小，以及rejectedExecution方法中使用的TimeUnit的睡眠时间，直到你实现：1）在最高级别获得稳定的性能（由线程池中的最佳线程数决定），2）避免在IO线程中浪费CPU（由每次尝试将被拒绝的事件重新排队到线程池之间的最佳睡眠时间决定）。
					</p>
				</div>
			</div>

			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-4"></a>24.2.4. 选择底层事件而非单个字段</h3>
						</div>
					</div>
				</div>
				<p>通过在select子句中选择底层事件，你可以减轻运行时的负载，因为运行时无需为每个输入事件生成一个新的输出事件。</p>
				<p>例如，以下语句将底层事件返回给更新监听器：</p>
				<pre class="">// 性能更好
select * from RFIDEvent</pre>
				<p>相比之下，下一条语句选择单个属性。此语句要求运行时生成一个输出事件，该事件恰好包含所需的属性：</p>
				<pre class="">// 性能较差
select assetId, zone, xlocation, ylocation from RFIDEvent </pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-5"></a>24.2.5. 优先使用流级过滤而非where子句过滤</h3>
						</div>
					</div>
				</div>
				<p>运行时的流级过滤经过了很好的优化，而通过where子句在任何数据窗口之后进行的过滤则未经过优化。</p>
				<p>对于命名窗口也是如此。如果你的应用程序只对命名窗口数据的一个子集感兴趣，并且这些过滤器与到达的事件不相关，请将过滤器放在命名窗口名称后的括号中。</p>
				<div class="sect3" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h4 class="title"><a id="perf-tips-5-nonnamedwindowexample"></a>24.2.5.1. 无命名窗口的示例</h4>
							</div>
						</div>
					</div>
					<p>考虑下面的示例，它执行流级过滤：</p>
					<pre class="">// 性能更好：流级过滤
select * from MarketData(ticker = 'GOOG')</pre>
					<p>下面的示例是等效（语义相同）的语句，并且在没有数据窗口的情况下执行数据窗口后的过滤。 编译器不会优化在where子句中进行过滤的语句，因为通常会存在数据窗口。</p>
					<pre class="">// 性能较差：数据窗口后过滤
select * from Market where ticker = 'GOOG'</pre>
					<p>因此，这种优化技术适用于没有任何数据窗口的语句。</p>
					<p>当使用数据窗口时，语义会发生变化。让我们看一个示例以更好地理解差异： 在下一条语句中，只有谷歌（GOOG）的市场事件进入长度窗口：</p>
					<pre class="">select avg(price) from MarketData(ticker = 'GOOG')#length(100)</pre>
					<p>上述语句计算了过去100个谷歌市场数据事件中谷歌市场数据事件的平均价格。</p>
					<p>将过滤器的位置与where子句中的过滤器进行比较。 以下语句不等效，因为所有事件都进入数据窗口（不只是谷歌事件）：</p>
					<pre class="">select avg(price) from Market#length(100) where ticker = 'GOOG'</pre>
					<p>上述语句计算了过去100个市场数据事件中所有市场数据事件的平均价格，并且仅输出谷歌（GOOG）的结果。</p>
				</div>
				<div class="sect3" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h4 class="title"><a id="perf-tips-5-namedwindowexample"></a>24.2.5.2. 使用命名窗口的示例</h4>
							</div>
						</div>
					</div>
					<p>接下来的两个示例语句将账号过滤器标准直接放在命名窗口名称后面的括号中：</p>
					<pre class="">// 性能更好：流级过滤
select * from WithdrawalNamedWindow(accountNumber = '123')</pre>
					<pre class="">// 性能更好：带有子查询的示例
select *, (select * from LoginSucceededWindow(accountNumber = '123'))
from WithdrawalNamedWindow(accountNumber = '123')</pre>
				</div>
				<div class="sect3" lang="en-US">
					<div class="titlepage">
						<div>
							<div>
								<h4 class="title"><a id="perf-tips-5-whereclausecomputations"></a>24.2.5.3. where子句中的常见计算</h4>
							</div>
						</div>
					</div>
					<p>如果你有许多语句对传入事件执行特定计算，考虑将计算从where子句移到作为流级过滤标准一部分列出的插件用户定义函数中。 编译器会优化过滤器中用户定义函数的求值，以便即使存在N条语句，传入事件也只需进行一次计算。</p>
					<pre class="">// 优先使用带有用户定义函数的流级过滤
select * from MarketData(vstCompare(*))</pre>
					<pre class="">// 当有N条类似语句时不太可取：
// 将where子句中的计算移到“vstCompare”函数中。
select * from MarketData where (VST * RT) – (VST / RT) > 1</pre>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-6"></a>24.2.6. 减少表达式中算术运算的使用</h3>
						</div>
					</div>
				</div>
				<p>编译器和运行时尚未对产生常量结果的算术表达式进行预求值，但是由于编译器生成字节码，JVM字节码优化会发生，并且可能会预求值某些表达式。</p>
				<p>因此，下面的过滤表达式是经过优化的：</p>
				<pre class="">// 性能更好：无算术运算
select * from MarketData(price>40) </pre>
				<p>而编译器目前无法优化此表达式：</p>
				<pre class="">// 性能较差：带有算术运算
select * from MarketData(price+10>50) </pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-6a"></a>24.2.7. 删除不必要的结构</h3>
						</div>
					</div>
				</div>
				<p>
					如果你的语句使用<code class="literal">order by</code>对输出事件进行排序，除非你的应用程序确实需要对接收的事件进行排序，否则考虑删除<code class="literal">order by</code>。
				</p>
				<p>
					如果你的语句指定了<code class="literal">group by</code>但未使用聚合函数，考虑删除<code class="literal">group by</code>。
				</p>
				<p>
					如果你的语句指定了<code class="literal">group by</code>，但过滤条件只允许一个组，考虑删除<code class="literal">group by</code>：
				</p>
				<pre class="">// 推荐：
select * from MarketData(symbol = 'GE') having sum(price) > 1000

// 不要使用这个，因为过滤器指定了单个符号：
select * from MarketData(symbol = 'GE') group by symbol having sum(price) > 1000</pre>
				<p>
					如果你的语句指定了分组数据窗口<code class="literal">#groupwin</code>，但被分组的窗口无论分组情况如何都保留相同的事件集， 则删除<code class="literal">#groupwin</code>，例如：
				</p>
				<pre class="">// 推荐：
create window MarketDataWindow#keepall as MarketDataEventType

// 不要使用这个，因为保留所有事件
// 或按符号保留所有事件是一回事：
create window MarketDataWindow#groupwin(symbol)#keepall as MarketDataEventType

// 不要使用这个，因为保留过去1分钟的事件
// 或按符号保留1分钟的事件是一回事：
create window MarketDataWindow#groupwin(symbol)#time(1 min) as MarketDataEventType</pre>
				<p>没有必要为每个流都指定一个数据窗口。</p>
				<pre class="">// 推荐：
select * from MarketDataWindow

// 如果只是监听事件则不需要数据窗口，推荐上面的写法
select * from MarketDataWindow#lastevent</pre>
				<p>如果你的语句指定了唯一数据窗口，但过滤条件只允许一个唯一条件，考虑删除唯一数据窗口：</p>
				<pre class="">// 推荐：
select * from MarketDataWindow(symbol = 'GE')#lastevent

// 如果你的过滤器指定了单个值，则不需要唯一键数据窗口
select * from MarketDataWindow(symbol = 'GE')#unique(symbol)</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-6b"></a>24.2.8. 结束模式子表达式</h3>
						</div>
					</div>
				</div>
				<p>
					在模式中，<code class="literal">every</code>关键字与后跟的(<code class="literal">-></code>) 结合使用时，每次匹配都会启动一个新的子表达式。
				</p>
				<p>例如，以下模式为每个到达的A事件启动一个寻找B事件的子表达式。</p>
				<pre class="">every A -> B</pre>
				<p>确定在什么条件下子表达式应该结束，以便运行时可以停止寻找B事件。这里有几个通用示例：</p>
				<pre class="">every A -> (B and not C)
every A -> B where timer:within(1 sec)</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-7"></a>24.2.9. 考虑使用EventPropertyGetter快速访问事件属性</h3>
						</div>
					</div>
				</div>
				<p>EventPropertyGetter接口对于在给定与获取属性的事件类型相同的EventBean实例的情况下，无需进行属性名表查找即可获取事件属性值非常有用。</p>
				<p>编译语句时，EPStatement实例通过getEventType()方法让我们知道事件类型。 从事件类型中，你可以获取命名事件属性的EventPropertyGetter实例。</p>
				<p>为了演示，考虑以下简单语句：</p>
				<pre class="">select symbol, avg(price) from Market group by symbol</pre>
				<p>编译并部署模块后，获取事件类型并将类型传递给监听器：</p>
				<pre class="">EPStatement stmt = runtime.getDeploymentService().getStatement(deploymentId, statementName);
MyGetterUpdateListener listener = new MyGetterUpdateListener(stmt.getEventType());</pre>
				<p>监听器可以使用该类型获取相同类型事件的属性值的快速获取器：</p>
				<pre class="">public class MyGetterUpdateListener implements StatementAwareUpdateListener {
    private final EventPropertyGetter symbolGetter;
    private final EventPropertyGetter avgPriceGetter;

    public MyGetterUpdateListener(EventType eventType) {
        symbolGetter = eventType.getGetter("symbol");
        avgPriceGetter = eventType.getGetter("avg(price)");
    }</pre>
				<p>最后，update方法可以调用获取器以获取事件属性值：</p>
				<pre class="">    public void update(EventBean[] eventBeans, EventBean[] oldBeans, EPStatement epStatement, EPRuntime runtime) {
        String symbol = (String) symbolGetter.get(eventBeans[0]);
        long volume = (Long) volumeGetter.get(eventBeans[0]);
        // 这里还有一些其他逻辑
    }</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-8"></a>24.2.10. 考虑转换底层事件</h3>
						</div>
					</div>
				</div>
				<p>当应用程序需要大多数或所有事件属性的值时，通常最好通过通配符简单地选择底层事件，并转换接收到的事件。</p>
				<p>让我们看一下示例语句：</p>
				<pre class="">select * from MarketData(symbol regexp 'E[a-z]')</pre>
				<p>该语句的更新监听器可能希望将接收到的事件转换为预期的底层事件类：</p>
				<pre class="">    public void update(EventBean[] eventBeans, EventBean[] eventBeans) {
        MarketData md = (MarketData) eventBeans[0].getUnderlying();
        // 这里还有一些其他逻辑
    }</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-9"></a>24.2.11. 关闭日志记录和审计</h3>
						</div>
					</div>
				</div>
				<p>即使你没有设置log4j配置文件，运行时也会确保最小化执行路径日志记录开销。 对于以前的版本，为了总体上减少日志记录开销，我们建议使用“WARN”日志级别或“INFO”日志级别。</p>
				<p>请参阅“etc/infoonly_log4j.xml”中的log4j配置文件，以获取log4j设置示例。</p>
				<p>
					EPL为语句提供了<code class="literal">@Audit</code>注解。对于性能测试和生产部署，我们建议删除<code class="literal">@Audit</code>。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-11"></a>24.2.12. 调整或禁用交付顺序保证</h3>
						</div>
					</div>
				</div>
				<p>如果你的应用程序不是多线程应用程序，或者你的应用程序对结果事件交付给应用程序监听器的顺序不敏感，那么考虑禁用运行时向监听器提供的有序交付结果的交付顺序保证：</p>
				<pre class="">Configuration config = new Configuration();
config.getRuntime().getThreading().setListenerDispatchPreserveOrder(false);</pre>
				<p>
					如果你的应用程序不是多线程应用程序，或者你的应用程序使用<code class="literal">insert into</code>子句使一个语句的结果可供进一步使用的语句使用，但不需要从产生结果的语句到使用结果的语句的有序交付，你可以禁用语句之间的交付顺序保证：
				</p>
				<pre class="">Configuration config = new Configuration();
config.getRuntime().getThreading().setInsertIntoDispatchPreserveOrder(false);</pre>
				<p>如果你的应用程序仅声明无状态语句，那么此处描述的设置并不相关。</p>
				<p>还有其他可用的配置选项，并在配置部分进行了描述，这些选项指定了超时值以及自旋或线程上下文切换。</p>
				<p>
					当启用对监听器的保证交付顺序并且自旋锁时间超过默认或配置的超时时间时，运行时日志将记录以下信息消息：<code class="literal">Spin wait timeout exceeded in listener dispatch</code>。 从<code class="literal">insert into</code>语句到使用语句的交付的相应消息是<code class="literal">Spin wait timeout exceeded in insert-into dispatch</code>。
				</p>
				<p>如果你的应用程序看到自旋锁时间已超过的消息，你的应用程序有几种选择：首先，可以选择禁用保留顺序。其次，确保你的监听器在返回之前不执行（长时间运行的）阻塞操作，例如通过在单独的线程中执行输出事件处理。第三，将超时值更改为更大的数字，以便在不记录消息的情况下阻塞更长时间。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-12"></a>24.2.13. 使用订阅者对象接收事件</h3>
						</div>
					</div>
				</div>
				<p>
					订阅者对象是一种接收结果数据的技术，与<code class="literal">UpdateListener</code>接口相比具有性能优势。请参阅 <a class="xref" href="apiruntime.html#apiruntime-statement-subscriber" title="16.5.2. 设置订阅者对象">16.5.2节，“设置订阅者对象”</a>。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-12b"></a>24.2.14. 考虑数据流</h3>
						</div>
					</div>
				</div>
				<p>
					数据流提供了一种高性能的方式来执行EPL选择语句，并使用其他内置的数据流操作符。数据流<code class="literal">Emitter</code>操作符允许将底层事件对象直接发送到数据流中。因此，运行时无需将每个底层事件包装到<code class="literal">EventBean</code>实例中，并且运行时无需将事件与语句进行匹配。相反，底层事件直接仅应用于你的应用程序提交事件的那个数据流实例，并且没有其他语句或数据流会看到相同的事件。
				</p>
				<p>
					数据流在 <a class="xref" href="dataflow.html" title="Chapter 21. EPL Reference: Data Flow">第21章，<i>EPL参考：数据流</i></a> 中进行了描述。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13"></a>24.2.15. 高到达率流和单个语句</h3>
						</div>
					</div>
				</div>
				<p>上下文分区与某些上下文分区状态相关联，该状态由当前聚合值、部分模式匹配以及数据窗口组成，具体取决于你的语句是否使用此类结构。当运行时接收到事件时，它会在锁定的情况下更新上下文分区状态，以便在并发多线程访问下上下文分区状态保持一致。</p>
				<p>对于高流量流，保护上下文分区状态所需的锁定可能会减慢速度，或者对于应用于完全相同的上下文分区及其状态的非常高的事件到达率引入阻塞。</p>
				<p>你的首选应该是使用允许多个上下文分区的上下文，例如哈希分段上下文。与键控分段上下文相比，哈希分段上下文通常性能更好，因为在键控分段上下文中，运行时必须检查对于给定的键是否存在分区或是否必须创建分区。</p>
				<p>你的第二个选择是将语句拆分为多个语句，每个语句执行部分预期功能，或者每个语句查找高到达率流的某个子集。每个语句在内存或CPU资源方面的成本非常低，运行时通常可以像处理单个语句一样高效地处理大量语句。</p>
				<p>例如，考虑以下语句：</p>
				<pre class="">// 在高度多线程环境中效果较差 
select venue, ccyPair, side, sum(qty)
from CumulativePrice
where side='O'
group by venue, ccyPair, side</pre>
				<p>如API部分所述，运行时通过为每个上下文分区使用单独的锁来保护每个上下文分区的状态。在高度多线程的应用程序中，线程可能会在特定的上下文分区上阻塞。因此，你会希望使用多个上下文分区。</p>
				<p>考虑创建哈希分段上下文或键控分段上下文。在哈希分段上下文中，传入数据只需使用少量计算分配到其中一个存储桶中。 在键控分段上下文中，运行时必须检查键以查看分区是否已经存在，或者是否必须分配新的分区。我们将在下面讨论这两种情况。 对于这两种类型的上下文，由于锁定是在上下文分区级别进行的，因此运行时获取的锁非常细粒度，允许进行高度并发的处理。</p>
				<p>此示例EPL声明了一个哈希分段上下文。在哈希分段上下文中，运行时可以预分配上下文分区，因此无需检查分区是否已经存在。在哈希分段上下文中，运行时只需根据哈希函数和取模运算的结果将事件分配给上下文分区。</p>
				<pre class="">create context MyContext coalesce by consistent_hash_crc32(venue) from CumulativePrice(side='O') granularity 16 preallocate</pre>
				<p>
					此示例EPL声明了一个键控分段上下文。键控分段上下文指示运行时为每个<code class="literal">venue, ccyPair, side</code>键组合使用一个上下文分区。运行时必须为每个事件检查对于该<code class="literal">venue</code>、<code class="literal">ccyPair</code>和<code class="literal">side</code>的组合是否存在分区：
				</p>
				<pre class="">create context MyContext partition by venue, ccyPair, side from CumulativePrice(side='O')</pre>
				<p>
					使用<code class="literal">create context</code>声明上下文后，请确保你的所有语句，包括那些创建命名窗口和表的语句，都指定该上下文。这是通过在每个语句前加上<code class="literal">context </code><span class="emphasis"><em>context_name</em></span><code class="literal"> ....</code>来完成的。
				</p>
				<p>
					下面是引用上述创建的上下文的新语句。请注意<code class="literal">context MyContext</code>，它告诉运行时此语句是在上下文分区的情况下执行的。必须提供此内容，否则该语句不会在上下文分区的情况下执行。
				</p>
				<pre class="">context MyContext select venue, ccyPair, side, sum(qty) from CumulativePrice</pre>
				<p>
					出于测试目的，或者如果你的应用程序控制并发性，你可以禁用上下文分区锁定，请参阅 <a class="xref" href="configuration.html#configuration-runtime-execution-disablelock" title="17.6.10.4. 禁用锁定">17.6.10.4节，“禁用锁定”</a>。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13a"></a>24.2.16. 子查询与连接、where子句和数据窗口</h3>
						</div>
					</div>
				</div>
				<p>
					连接流时，运行时会根据<code class="literal">where</code>子句构建连接数据窗口的笛卡尔积。它在语句编译时分析<code class="literal">where</code>子句，并构建适当的索引和查询策略。避免在连接的<code class="literal">where</code>子句中使用需要求值的表达式，例如用户定义函数或算术表达式。
				</p>
				<p>
					连接流且不提供<code class="literal">where</code>子句时，考虑使用<code class="literal">#unique</code>数据窗口或<code class="literal">#lastevent</code>数据窗口，以便仅连接每个流的最后一个事件或每个唯一键的最后一个事件。
				</p>
				<p>当两个数据窗口都已填满并且其中一个流有事件到达时，下面的示例语句最多可以生成5000行：</p>
				<pre class="">// 避免在没有where子句的数据窗口之间的流连接
select * from StreamA#length(100), StreamB#length(50)</pre>
				<p>
					考虑使用子查询，考虑使用带有insert-into的单独语句，并考虑提供<code class="literal">where</code>子句以限制行的笛卡尔积。
				</p>
				<p>
					下面的示例展示了不同的方法，假设定义了一个具有属性symbol和value的<code class="literal">MyEvent</code>，这些方法在语义上并不等效：
				</p>
				<pre class="">// 替换以下语句，因为它可能性能不佳
select a.symbol, avg(a.value), avg(b.value) 
from MyEvent#length(100) a, MyEvent#length(50) b

// 带where子句的连接
select a.symbol, avg(a.value), avg(b.value) 
from MyEvent#length(100) a, MyEvent#length(50) b 
where a.symbol = b.symbol

// 带where子句的单向连接
select a.symbol, avg(b.value) 
from MyEvent unidirectional, MyEvent#length(50) b 
where a.symbol = b.symbol

// 子查询
select 
  (select avg(value) from MyEvent#length(100)) as avgA, 
  (select avg(value) from MyEvent#length(50)) as avgB,
  a.symbol
from MyEvent

// 由于流几乎不消耗资源，使用insert-into填充并进行单向连接 
insert into StreamAvgA select symbol, avg(value) as avgA from MyEvent#length(100)
insert into StreamAvgB select symbol, avg(value) as avgB from MyEvent#length(50)
select a.symbol, avgA, avgB from StreamAvgA unidirectional, StreamAvgB#unique(symbol) b
where a.symbol = b.symbol</pre>
				<p>连接是双向求值的：当参与连接的任何一个流的事件到达时，连接就会被求值，除非使用了单向关键字。 当仅在特定事件到达时才需要进行求值时，考虑使用子查询代替：</p>
				<pre class="">// 重写此连接，因为当LoginSucceededWindow到达时不需要进行连接
// 同时重写，因为账号总是值为123。
select * from LoginSucceededWindow as l, WithdrawalWindow as w
where w.accountNumber = '123' and w.accountNumber = l.accountNumber

// 重写为子查询
select *, (select * from LoginSucceededWindow where accountNumber=’123’) 
from WithdrawalWindow(accountNumber=’123’) as w</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13b"></a>24.2.17. 模式和模式子表达式实例</h3>
						</div>
					</div>
				</div>
				<p>
					模式中的<code class="literal">every</code>和重复操作符控制着活动的子表达式的数量。每个子表达式都可能消耗内存，因为根据模式中标签的使用情况，它可能会保留匹配的事件。大量活动的子表达式可能会降低性能，甚至导致内存不足错误。
				</p>
				<p>
					在设计模式语句时，考虑使用<code class="literal">timer:within</code>来减少子表达式的存活时间，或者考虑使用<code class="literal">not</code>操作符来结束子表达式。
				</p>
				<p>
					这里的示例假设存在一个<code class="literal">AEvent</code>和一个<code class="literal">BEvent</code>事件类型，它们具有一个<code class="literal">id</code>属性，该属性可能在这两种事件类型的到达事件之间存在关联。
				</p>
				<p>
					在下面的示例模式中，对于每个到达的AEvent，运行时都会启动一个新的模式子表达式，以寻找匹配的BEvent。由于AEvent被标记为<code class="literal">a</code>，运行时会保留每个AEvent，直到找到匹配项并交付给监听器或订阅者：
				</p>
				<pre class="">every a=AEvent -> b=BEvent(b.id = a.id)</pre>
				<p>结束子表达式的一种方法是附加一个它可以活动的时间长度。</p>
				<p>下一条语句在启动子表达式的AEvent事件到达10秒后，结束寻找匹配BEvent的子表达式：</p>
				<pre class="">every a=AEvent -> (b=BEvent(b.id = a.id) where timer:within(10 sec))</pre>
				<p>
					结束子表达式的第二种方法是使用<code class="literal">not</code>操作符。当某个特定事件到达时，你可以将<code class="literal">not</code>操作符与<code class="literal">and</code>操作符一起使用来结束子表达式。
				</p>
				<p>下一条语句在启动子表达式的AEvent事件之后到达的下一个BEvent与AEvent的id不匹配时，结束寻找匹配BEvent的子表达式：</p>
				<pre class="">every a=AEvent -> (b=BEvent(b.id = a.id) and not BEvent(b.id != a.id))</pre>
				<p>
					<code class="literal">every-distinct</code>操作符可用于为一个或多个键保持一个子表达式处于活动状态。下一个模式展示了<code class="literal">every-distinct</code>的一种替代方法。当到达的AEvent与启动子表达式的AEvent的id匹配时，它会结束寻找匹配BEvent的子表达式：
				</p>
				<pre class="">every a=AEvent -> (b=BEvent(b.id = a.id) and not AEvent(b.id = a.id))</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13b2"></a>24.2.18. 模式子表达式实例与数据窗口的使用</h3>
						</div>
					</div>
				</div>
				<p>对于某些用例，你可以指定一个或多个数据窗口作为解决方案，或者你也可以指定一个模式来解决你的用例。</p>
				<p>对于模式，你应该了解运行时采用的是动态状态机。对于数据窗口，运行时采用的是增量网络和集合。一般来说，你可能会发现需要大量子表达式实例的模式比数据窗口消耗更多的内存和CPU资源。</p>
				<p>例如，考虑以下语句，它过滤掉在20秒内出现的重复交易id：</p>
				<pre class="">select * from TxnEvent#firstunique(transactionId)#time(20 sec)</pre>
				<p>你也可以使用模式来解决这个问题：</p>
				<pre class="">select * from pattern [every-distinct(a.transactionId) a=TxnEvent where timer:within(20 sec)]</pre>
				<p>如果你需要跟踪大量不同的交易id，你可能会发现模式的性能不如数据窗口解决方案，因为模式要求运行时为每个交易id管理一个模式子表达式。数据窗口解决方案要求运行时管理过期情况，在许多情况下这可以提供更好的性能。</p>
				<p>
					与此类似，一般来说，使用EPL连接语法优于使用基数检测关系的模式，即<code class="literal">pattern [every-distinct(...) ... -> every-distinct(...) ...]</code>。连接查询规划是一种强大的编译器和运行时特性，它实现了快速的关系连接。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13c"></a>24.2.19. 全保留数据窗口</h3>
						</div>
					</div>
				</div>
				<p>
					<code class="literal">#keepall</code>数据窗口是一种保留所有到达事件的数据窗口。在开发阶段，这个数据窗口可能很有用，并且可以使用<code class="literal">on-delete</code>和命名窗口来实现自定义的过期策略。然而，应该注意及时从全保留数据窗口中删除数据。使用<code class="literal">on-select</code>或即发即弃查询来统计当前由具有全保留过期策略的命名窗口持有的行数。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-13d"></a>24.2.20. 降低内存消耗的语句设计 - 诊断内存不足错误</h3>
						</div>
					</div>
				</div>
				<p>本节描述了内存不足问题的常见来源。</p>
				<p>如果使用全保留数据窗口，请考虑上述信息。如果使用模式语句，请考虑本节前面讨论的模式子表达式的实例化和生命周期。</p>
				<p>
					当使用<code class="literal">group-by</code>子句或<code class="literal">#groupwin</code>分组数据窗口时，请考虑以下提示。确保你的分组标准是那些可能值数量有限的字段，否则请指定提示。
				</p>
				<p>
					<code class="literal">#unique</code>唯一数据窗口也可能是错误的来源。如果你的唯一性标准包括一个永远不唯一的字段，除非你的应用程序删除事件，否则数据窗口的内存使用量可能会增加。
				</p>
				<p>
					当使用由生成无限数量不同值的不同值表达式参数化的<code class="literal">every-distinct</code>模式构造时，请考虑指定一个时间段作为参数的一部分，以向运行时指示一个不同值应被考虑多长时间。
				</p>
				<p>
					在匹配识别模式中，如果可选事件是<code class="literal">measures</code>子句中报告的数据的一部分，请考虑限制可选事件的数量。同样，当使用分区子句时，如果你的分区标准包括一个永远不唯一的字段，匹配识别运行时的内存使用量可能会增加。
				</p>
				<p>内存使用的另一个来源是当你的应用程序部署模块，但在不再需要这些模块时未能取消部署。</p>
				<p>在你的应用程序设计中，当应用程序监听器或订阅者对象保留输出数据时，你也需要留意。</p>
				<p>
					由<code class="literal">runtime URI</code>唯一标识的运行时是一个相对重量级的对象。理想情况下，你的应用程序每个JVM分配的运行时实例应少于一千个（1000个）。 一个语句实例与一个运行时实例相关联，由语句名称唯一标识，是一个中等重量级的对象。我们看到过应用程序轻松分配100,000个语句。 一个语句的上下文分区实例与一个语句相关联，由上下文分区id唯一标识，是一个轻量级的对象。我们看到过应用程序为100个语句轻松分配5000个上下文分区，即5,000,000个上下文分区。 一个聚合行、数据窗口行、模式等与一个语句上下文分区相关联，并且其本身是一个非常轻量级的对象。
				</p>
				<p>
					<code class="literal">prev</code>、<code class="literal">prevwindow</code>和<code class="literal">prevtail</code>函数直接访问数据窗口。运行时不需要维护单独的数据结构，并且分组是基于<code class="literal">#groupwin</code>分组数据窗口的使用。 将此与使用诸如<code class="literal">first</code>、<code class="literal">window</code>和<code class="literal">last</code>等事件聚合函数进行比较，这些函数根据<code class="literal">group by</code>子句进行分组。如果你的语句同时使用了这两者，请考虑重新表述以使用<code class="literal">prev</code>代替。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-14"></a>24.2.21. 性能、JVM、操作系统和硬件</h3>
						</div>
					</div>
				</div>
				<p>
					性能还取决于你的JVM（如Sun HotSpot、BEA JRockit、IBM J9）、你的操作系统和你的硬件。 可以使用诸如<a class="link" href="http://www.spec.org" target="">spec.org</a>上的specJBB这样的JVM性能指标。对于内存密集型语句，你可能需要考虑64位架构，它可以处理超过2GB或3GB的内存，尽管由于更复杂的指针地址管理，64位JVM通常会带来性能上的损失。
				</p>
				<p>
					JVM、操作系统和硬件的选择取决于许多因素，因此很难给出明确的建议。 选择取决于语句的数量和线程的数量。 大量的线程将受益于更多的CPU和核心。如果你有非常低的延迟要求，你应该考虑每个核心获得更高的GHz，并且可能需要软实时JVM来在JVM级别强制垃圾回收的确定性，甚至可以考虑像Azul这样的专用硬件。 如果你的语句使用了大型数据窗口，将使用更多的RAM和堆空间，因此你应该明确地进行规划并考虑到这一点，并且可能考虑64位架构或考虑 <a class="link" href="http://www.espertech.com/products/" target="">EsperHA</a>。
				</p>
				<p>语句的数量和类型是一个不能一概而论的因素。 基准测试工具包可以帮助测试一些要求并建立基线，对于更复杂的用例，模拟或概念验证肯定是最好的方法。 EsperTech的专家可以在咨询关系中帮助编写接口。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-15"></a>24.2.22. 考虑使用提示</h3>
						</div>
					</div>
				</div>
				<p>
					@Hint注解提供一个关键字或逗号分隔的关键字列表，这些关键字为编译器和运行时提供有关语句执行的指令，这些指令会影响语句的运行时性能和内存使用。另请参阅 <a class="xref" href="epl_clauses.html#epl-syntax-annotation-hint" title="5.2.7.9. @Hint">5.2.7.9节，“@Hint”</a>。
				</p>
				<p>
					一般的查询规划在 <a class="xref" href="performance.html#perf-tips-24" title="24.2.31. 关于查询规划的说明">24.2.31节，“关于查询规划的说明”</a> 中进行了描述。
				</p>
				<p>
					影响查询规划表达式分析的提示在 <a class="xref" href="performance.html#perf-tips-25a" title="24.2.32. 查询规划表达式分析提示">24.2.32节，“查询规划表达式分析提示”</a> 中进行了描述。
				</p>
				<p>
					影响查询规划索引选择的提示在 <a class="xref" href="performance.html#perf-tips-25b" title="24.2.33. 查询规划索引提示">24.2.33节，“查询规划索引提示”</a> 中进行了描述。
				</p>
				<p>
					与连接、外连接、单向连接、关系连接和非关系连接相关的更多提示在 <a class="xref" href="epl_clauses.html#epl-join-hints" title="5.12.6. 与连接相关的提示">5.12.6节，“与连接相关的提示”</a> 中进行了描述。
				</p>
				<p>
					用于<code class="literal">group by</code>以指定如何回收组状态的提示在 <a class="xref" href="epl_clauses.html#epl-groupby-hints" title="5.6.2.1. 与分组相关的提示">5.6.2.1节，“与分组相关的提示”</a> 和 <a class="xref" href="epl-views.html#view-std-groupwin" title="14.3.15. 分组数据窗口 (groupwin 或 std:groupwin)">14.3.15节，“分组数据窗口 (groupwin 或 std:groupwin)”</a> 中进行了描述。
				</p>
				<p>
					用于<code class="literal">group by</code>以指定无界流和时间戳组的聚合状态回收的提示在 <a class="xref" href="epl_clauses.html#epl-groupby-hints" title="5.6.2.1. 与分组相关的提示">5.6.2.1节，“与分组相关的提示”</a> 中进行了描述。
				</p>
				<p>
					用于<code class="literal">match_recognize</code>以指定仅迭代的提示在 <a class="xref" href="match-recognize.html#match-recognize-patternops-iterator" title="8.4.7. 消除重复匹配">8.4.7节，“消除重复匹配”</a> 中进行了描述。
				</p>
				<p>
					当你的子查询从命名窗口中选择数据时，为了优化子查询性能，请考虑 <a class="xref" href="epl_clauses.html#epl-subqueries-hints" title="5.11.8. 与子查询相关的提示">5.11.8节，“与子查询相关的提示”</a> 中讨论的提示。
				</p>
				<p>
					<code class="literal">@NoLock</code>提示用于删除上下文分区锁定（也请阅读注意事项），在 <a class="xref" href="apiruntime.html#apiruntime-threading" title="16.8. 运行时线程和并发">16.8节，“运行时线程和并发”</a> 中进行了描述。
				</p>
				<p>
					控制过滤表达式扩展的提示，在 <a class="xref" href="configuration.html#configuration-compiler-execution-filterservicemaxfilterwidth" title="17.5.8.1. 过滤服务最大过滤宽度">17.5.8.1节，“过滤服务最大过滤宽度”</a> 中进行了进一步描述。
				</p>
			</div>

			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-16"></a>24.2.23. 优化流过滤表达式</h3>
						</div>
					</div>
				</div>
				<p>假设你的语句在流过滤器中调用一个静态方法，如下述语句示例所示：</p>
				<pre class="">select * from MyEvent(MyHelperLibrary.filter(field1, field2, field3, field4*field5))</pre>
				<p>
					作为启动上述语句的结果，运行时必须计算每个MyEvent事件，调用<code class="literal">MyHelperLibrary.filter</code>方法并传递某些事件属性。对于指定要计算的函数的模式过滤器，情况也是如此。
				</p>
				<p>
					如果可能的话，考虑将函数执行的一些检查移回到过滤器中，或者考虑将函数拆分为由<code class="literal">and</code>连接词分隔的两部分。一般来说，对于所有表达式，运行时首先计算<code class="literal">and</code>左边的表达式，并且在第一个表达式返回false的情况下，可以跳过对连接词中进一步表达式的计算。此外，编译器可以确定过滤索引字段，并且运行时可以为流或模式过滤器中提供的字段构建过滤索引。
				</p>
				<p>例如，下面的语句可能计算速度更快：</p>
				<pre class="">select * from MyEvent(field1="value" and 
  MyHelperLibrary.filter(field1, field2, field3, field4*field5))</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-17"></a>24.2.24. 语句和运行时指标报告</h3>
						</div>
					</div>
				</div>
				<p>
					你可以按照 <a class="xref" href="apiruntime.html#apiruntime-instrumentation" title="16.12. 运行时和语句指标报告">16.12节，“运行时和语句指标报告”</a> 中所述，使用语句和运行时指标报告来监控性能或识别运行缓慢的语句。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-18"></a>24.2.25. 表达式计算顺序和提前退出</h3>
						</div>
					</div>
				</div>
				<p>“提前退出”或“短路计算” 这个术语是指运行时可以在不完整计算所有子表达式的情况下计算一个表达式。</p>
				<p>考虑如下表达式：</p>
				<pre class="">where expr1 and expr2 and expr3</pre>
				<p>
					如果expr1为false，运行时就不需要计算expr2和expr3。因此，当使用<code class="literal">AND</code>逻辑运算符时，考虑重新排列表达式，将最具选择性的表达式放在首位，然后是选择性较低的表达式。
				</p>
				<p>
					对于<code class="literal">OR</code>逻辑运算符也是如此：如果expr1为true，运行时就不需要计算expr2和expr3。因此，当使用<code class="literal">OR</code>逻辑运算符时，考虑重新排列表达式，将选择性最低的表达式放在首位，然后是选择性较高的表达式。
				</p>
				<p>表达式的顺序（这里是expr1、expr2和expr3）对于连接和子查询查询规划器来说没有影响。</p>
				<p>请注意，运行时并不保证在所有情况下都进行短路计算。运行时可能会将where子句或过滤条件重写为另一种计算顺序，以便它可以执行索引查找。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-19"></a>24.2.26. 大量线程</h3>
						</div>
					</div>
				</div>
				<p>
					当运行时使用大量线程时，例如超过100个线程，你可以在配置中提供一个设置，指示运行时减少线程局部变量的使用。更多信息请参阅 <a class="xref" href="configuration.html#configuration-runtime-execution" title="17.6.10. 与语句执行相关的运行时设置">17.6.10节，“与语句执行相关的运行时设置”</a>。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-19a"></a>24.2.27. 过滤计算调优</h3>
						</div>
					</div>
				</div>
				<p>
					我们提供了一个开关，用于针对过滤器调优传入事件的计算。更多信息请参阅 <a class="xref" href="configuration.html#configuration-runtime-execution" title="17.6.10. 与语句执行相关的运行时设置">17.6.10节，“与语句执行相关的运行时设置”</a>。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-20"></a>24.2.28. 上下文分区相关信息</h3>
						</div>
					</div>
				</div>
				<p>由于运行时在上下文分区级别进行锁定，通过使用上下文分区可以在多线程下实现高并发性。</p>
				<p>
					一般来说，上下文分区比通过<code class="literal">group by</code>或<code class="literal">#groupwin</code>实现的更细粒度的分组需要更多的内存。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-21"></a>24.2.29. 优先使用常量变量而非非常量变量</h3>
						</div>
					</div>
				</div>
				<p>create-variable语法以及API可以将一个变量标识为常量值。当一个变量的值不打算改变时，最好将该变量声明为常量。</p>
				<p>例如，考虑以下两个语句，每个语句都声明一个变量。第一个语句声明一个常量变量，第二个语句声明一个非常量变量：</p>
				<pre class="">// 声明一个常量变量
create constant variable string CONST_DEPARTMENT = 'PURCHASING'</pre>
				<pre class="">// 声明一个非常量变量
create variable string VAR_DEPARTMENT = 'SALES'</pre>
				<p>当你的应用程序编译一个根据变量值对事件进行过滤的语句时，编译器会在内部检查这样的表达式，并对常量变量进行过滤优化，这种优化在计算中更有效。</p>
				<p>例如，考虑以下两个语句，每个语句都查找与属于给定部门的人员相关的事件：</p>
				<pre class="">// 优先使用常量
select * from PersonEvent(department=CONST_DEPARTMENT)</pre>
				<pre class="">// 效率较低
select * from PersonEvent(department=VAR_DEPARTMENT)</pre>
				<p>运行时可以更有效地计算使用声明为常量的变量的表达式。对于子查询和连接查询规划，也可以观察到同样的情况。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-22"></a>24.2.30. 优先使用POJO事件或选择对象数组事件</h3>
						</div>
					</div>
				</div>
				<p>POJO（普通老式Java对象，遵循JavaBean规范）事件通常提供最佳性能，同时也具有最大的灵活性。</p>
				<p>然而，与Map类型的事件相比，对象数组事件在访问事件属性值时也提供了出色的读取访问性能。此外，对象数组事件比Map类型的事件使用的内存少得多。它们还提供了出色的写入访问性能。</p>
				<p>
					不同事件表示形式的比较请参阅 <a class="xref" href="event_representation.html#eventrep_comparing" title="3.5. 比较事件表示形式">3.5节，“比较事件表示形式”</a>。
				</p>
				<p>
					我们建议你的应用程序向运行时发送POJO或对象数组事件，而不是Map类型的事件。更多信息请参阅 <a class="xref" href="appendix_eventrepoa.html" title="Appendix F. 事件表示：对象数组 (Object[]) 事件">附录F，<i>事件表示：对象数组 (Object[]) 事件</i></a>。
				</p>
				<p>
					此外，我们建议考虑将应用程序的默认事件表示形式的编译器配置设置为对象数组，如 <a class="xref" href="configuration.html#configuration-common-eventmeta-representation" title="17.4.9.1. 默认事件表示形式">17.4.9.1节，“默认事件表示形式”</a> 中所述。或者，你可以在单个语句中使用<code class="literal">@EventRepresentation(objectarray)</code>注解。
				</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-24"></a>24.2.31. 关于查询规划的说明</h3>
						</div>
					</div>
				</div>
				<p>查询规划适用于子查询、连接（任何类型）、命名窗口和表的操作（on-select、on-merge、on-insert、on-update、on-select）以及即发即弃查询。查询规划会影响查询执行速度。启用查询计划日志记录以输出查询计划信息。</p>
				<p>对于查询规划，编译器从以下方面获取信息：</p>
				<div class="orderedlist">
					<ol>
						<li>
							<p>
								<code class="literal">where</code>子句（如果有指定）。<code class="literal">where</code>子句将流、模式、命名窗口、表等与更多的流、模式、表和命名窗口关联起来，因此是查询规划的主要信息来源。
							</p>
						</li>
						<li>
							<p>
								在流和命名窗口上声明的数据窗口。 <code class="literal">#unique</code>和<code class="literal">#firstunique</code>数据窗口指示编译器根据唯一标准保留最后一个事件。
							</p>
						</li>
						<li>
							<p>
								对于命名窗口和表，通过<code class="literal">create unique index</code>或<code class="literal">create index</code>创建的显式索引。
							</p>
						</li>
						<li>
							<p>对于命名窗口（而非表），之前创建的隐式索引。如果显式索引不符合关联要求，编译器可以计划自动创建隐式索引。</p>
						</li>
						<li>
							<p>
								为相关语句指定的任何提示，包括在使用<code class="literal">create window</code>创建命名窗口时指定的提示。
							</p>
						</li>
					</ol>
				</div>
				<p>编译器优先使用唯一索引而非非唯一索引。</p>
				<p>编译器优先使用基于哈希的查找（equals）以及哈希 - B树组合查找（equals和关系运算符或范围），而不是B树查找（关系运算符或范围），也优先于in关键字（单索引和多索引）查找计划。这种行为可以通过接下来讨论的提示来控制。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-25a"></a>24.2.32. 查询规划表达式分析提示</h3>
						</div>
					</div>
				</div>
				<p>表达式分析提示会影响任何执行连接或子查询的语句和即发即弃查询的查询规划。它们还会影响命名窗口和表的操作语句。</p>
				<p>
					此提示指示编译器应排除哪些表达式、运算符或流，因此在查询规划时不会考虑这些内容。 该提示适用于<code class="literal">where</code>子句，对于外连接，若存在<code class="literal">on</code>子句，则也适用于该子句。
				</p>
				<p>该提示将单个表达式作为其唯一参数，该参数置于括号内。该表达式必须返回一个布尔值。</p>
				<p>当提供的表达式对于给定的组合返回true时，该组合将不会被纳入查询计划的考虑范围。 一个组合由一个源流（名称或编号）、一个目标流（名称或编号）、一个运算符（即等于、关系运算符、in关键字）和一组表达式组成。</p>
				<div class="table">
					<a id="d0e58261"></a>
					<p class="title">
						<b>表24.1. 表达式分析提示的内置属性</b>
					</p>
					<div class="table-contents">
						<table summary="Built-In Properties of the Expression Analysis Hint" border="1">
							<colgroup>
								<col>
									<col>
										<col>
							</colgroup>
							<thead>
								<tr>
									<th>名称</th>
									<th>类型</th>
									<th>描述</th>
								</tr>
							</thead>
							<tbody>
								<tr>
									<td>exprs</td>
									<td>字符串数组 (<code class="literal">String[]</code>)
									</td>
									<td>经过最小化空白处理的表达式文本。</td>
								</tr>
								<tr>
									<td>from_streamname</td>
									<td>字符串</td>
									<td>由<code class="literal">as</code>关键字指定的提供查找值的流的名称。
									</td>
								</tr>
								<tr>
									<td>from_streamnum</td>
									<td>整数</td>
									<td>在from子句中列出的提供查找值的流的整数序号。</td>
								</tr>
								<tr>
									<td>opname</td>
									<td>字符串</td>
									<td>运算符名称。有效值为<code class="literal">equals</code>、<code class="literal">relop</code>（关系运算符和范围）和<code class="literal">inkw</code>（<code class="literal">in</code>关键字）。
									</td>
								</tr>
								<tr>
									<td>to_streamname</td>
									<td>字符串</td>
									<td>由<code class="literal">as</code>关键字指定的提供可索引值的流的名称。
									</td>
								</tr>
								<tr>
									<td>to_streamnum</td>
									<td>整数</td>
									<td>在from子句中列出的提供可索引值的流的整数序号。</td>
								</tr>
							</tbody>
						</table>
					</div>
				</div>
				<br class="table-break">
					<p>
						考虑两种事件类型A和B。事件类型A有一个属性<code class="literal">aprop</code>，事件类型B有一个属性<code class="literal">bprop</code>。假设A和B通过<code class="literal">aprop</code>和<code class="literal">bprop</code>相关联。
					</p>
					<p>所有A和B事件的内连接可能如下所示：</p> <pre class="">select * from A#keepall as a, B#keepall as b where aprop = bprop</pre>
					<p>
						在默认查询计划中，当一个A事件到来时，运行时获取<code class="literal">aprop</code>的值，并针对<code class="literal">bprop</code>的值执行索引查找，以获取匹配的B事件。反之，当一个B事件到来时，运行时获取<code class="literal">bprop</code>的值，并针对<code class="literal">aprop</code>的值执行索引查找，以获取匹配的A事件。
					</p>
					<p>编译器会为每个组合计算提示表达式。下表概述了提供给提示表达式的两行内容：</p>
					<div class="table">
						<a id="d0e58374"></a>
						<p class="title">
							<b>表24.2. 表达式分析提示的内置属性</b>
						</p>
						<div class="table-contents">
							<table summary="Built-In Properties of the Expression Analysis Hint" border="1">
								<colgroup>
									<col>
										<col>
											<col>
												<col>
													<col>
														<col>
								</colgroup>
								<thead>
									<tr>
										<th>exprs</th>
										<th>from_streamname</th>
										<th>from_streamnum</th>
										<th>opname</th>
										<th>to_streamname</th>
										<th>to_streamnum</th>
									</tr>
								</thead>
								<tbody>
									<tr>
										<td><code class="literal">["aprop", "bprop"]</code></td>
										<td><code class="literal">a</code></td>
										<td><code class="literal">0</code></td>
										<td><code class="literal">equals</code></td>
										<td><code class="literal">b</code></td>
										<td><code class="literal">1</code></td>
									</tr>
									<tr>
										<td><code class="literal">["bprop", "aprop"]</code></td>
										<td><code class="literal">b</code></td>
										<td><code class="literal">1</code></td>
										<td><code class="literal">equals</code></td>
										<td><code class="literal">a</code></td>
										<td><code class="literal">0</code></td>
									</tr>
								</tbody>
							</table>
						</div>
					</div>
					<br class="table-break">
						<p>以下带有提示的语句会导致分析器排除所有组合，因为传入的表达式始终返回true，实际上会导致查询规划器始终将该语句作为全表扫描来执行。</p> <pre class="">@hint('exclude_plan(true)')
select * from A#keepall as a, B#keepall as b where aprop = bprop</pre>
						<p>此提示指示编译器在查询规划时忽略所有等于运算符：</p> <pre class="">@hint('exclude_plan(opname="equals")') select ....</pre>
						<p>下一个提示指示编译器忽略从A到B的查找方向上的等于运算符：</p> <pre class="">@hint('exclude_plan(opname="equals" and from_streamname="a")') select ....</pre>
						<p>相反，此提示指示编译器忽略从B到A的查找方向上的等于运算符：</p> <pre class="">@hint('exclude_plan(opname="equals" and from_streamname="b")') select ....</pre>
						<p>
							使用<code class="literal">exprs</code>表达式文本数组来排除特定的表达式：
						</p> <pre class="">@hint('exclude_plan(exprs[0]="aprop")') select ....</pre>
						<p>对于子查询，流编号0是子查询的from子句本身，1到N是外围语句的from子句的流。 对于命名窗口和表的操作语句，流编号0是命名窗口或表，流编号1是触发模式或事件。</p>
						<p>要指定多个表达式，请指定多个提示。当任何一个提示表达式返回true时，编译器将排除特定的组合。</p>
						<p>
							要检查传递给提示表达式的值，请启用查询计划日志记录。要检查表达式计算，请使用<code class="literal">@Audit</code>。
						</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-25b"></a>24.2.33. 查询规划索引提示</h3>
						</div>
					</div>
				</div>
				<p>当前，索引提示仅支持以下类型的语句：</p>
				<div class="orderedlist">
					<ol>
						<li>
							<p>命名窗口和表的操作语句（on-select、on-merge、on-insert、on-update、on-select）。</p>
						</li>
						<li>
							<p>对启用了索引共享（默认禁用）的命名窗口进行子选择的语句。</p>
						</li>
						<li>
							<p>对表进行子选择的语句。</p>
						</li>
						<li>
							<p>即发即弃查询。</p>
						</li>
					</ol>
				</div>
				<p>
					对于上述语句，你可以指示编译器使用哪个显式索引（通过<code class="literal">create index</code>语法创建）。
				</p>
				<p>
					在<code class="literal">@Hint</code>和<code class="literal">index</code>字面量后面的括号中指定显式索引的名称。
				</p>
				<p>
					以下示例指示编译器尽可能使用<code class="literal">UserProfileIndex</code>：
				</p>
				<pre class="">@Hint('index(UserProfileIndex)')</pre>
				<p>
					添加字面量<code class="literal">bust</code>以指示编译器使用该索引，或者如果编译器无法使用该索引，则使查询规划因异常而失败，从而导致语句编译失败。
				</p>
				<p>
					以下示例指示编译器尽可能使用<code class="literal">UserProfileIndex</code>，或者如果无法使用该索引，则因异常而失败：
				</p>
				<pre class="">@Hint('index(UserProfileIndex, bust)')</pre>
				<p>
					可以列出多个索引，用逗号 (<code class="literal">,</code>) 分隔。
				</p>
				<p>
					下一个示例指示编译器考虑<code class="literal">UserProfileIndex</code>和<code class="literal">SessionIndex</code>，或者如果任何一个索引无法使用，则因异常而失败：
				</p>
				<pre class="">@Hint('index(UserProfileIndex, SessionIndex, bust)')</pre>
				<p>
					可以添加字面量<code class="literal">explicit</code>以指示编译器仅使用显式创建的索引。
				</p>
				<p>最后一个示例指示编译器考虑任何显式创建的索引，或者如果任何一个显式创建的索引无法使用，则因异常而失败：</p>
				<pre class="">@Hint('index(explicit, bust)')</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-26"></a>24.2.34. 测量吞吐量</h3>
						</div>
					</div>
				</div>
				<p>
					我们建议使用 <code class="literal">System.nanoTime()</code> 来测量处理一批（例如 1000 个）事件时所花费的时间。
				</p>
				<p>
					请注意，<code class="literal">System.nanoTime()</code> 提供纳秒级的精度，但不一定具有纳秒级的分辨率。
				</p>
				<p>
					因此，不要尝试测量运行时处理单个事件所花费的时间：<code class="literal">System.nanoTime()</code> 的分辨率不够。 此外，有报告称 <code class="literal">System.nanoTime()</code> 实际上可能会“倒流”，并且在多线程环境下可能并不总是按预期工作。 请查看你的 JVM 平台文档。
				</p>
				<p>在默认配置中，测量性能的最佳方法是记录开始纳秒时间，发送大量事件（例如 10000 个事件），然后再次记录纳秒时间，报告这两个时间的差值。</p>
				<p>如果你的配置设置了入站线程或其他线程选项，你应该要么监控队列深度来确定性能，要么在测量性能时禁用线程选项，要么让你的应用程序使用多个线程来发送事件。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-27"></a>24.2.35. 不要重复创建相同或相似的语句</h3>
						</div>
					</div>
				</div>
				<p>只创建一次语句并附加多个监听器，比重复创建相同的语句 X 次要高效得多。</p>
				<p>使用上下文声明来提取语句之间的共性，而不是创建 X 个相似的语句，这样会高效得多。</p>
				<p>EPL、编译器和运行时针对低延迟和高吞吐量执行进行了优化。为了实现这一点，编译器会进行分析和查询规划。每个语句中的某些信息（索引）可以在运行时有效共享，这样运行时就可以消除重复处理，从而实现低延迟和高吞吐量。代价是编译器必须为每个语句执行一些前期分析。</p>
				<p>由于你的目标是使所有测试代码尽可能真实、贴近实际生产环境，我们建议生产代码或测试代码不要多次部署完全相同的语句。相反，考虑只创建一次相同的语句并附加多个监听器。 编译器和运行时不会尝试检测重复的语句，因为你的应用程序可以轻松做到这一点。</p>
				<p>
					假设你的测试语句在 1 分钟的时间窗口内计算聚合，例如 <code class="literal">select symbol, count(*) from StockTick#time(1 min) group by symbol</code>。 如果你的代码创建了 100 次相同的语句，代码会指示运行时跟踪 100 个逻辑上独立的时间窗口，并为每个分组跟踪 100 次聚合。显然，这不是对 EPL 的有效使用，你的语句和代码设计可能不是最优的。
				</p>
				<p>考虑关系数据库的情况。你的代码可以连接到关系数据库，用不同的名称创建 100 次相同的表，并将相同的行数据填充到 100 个不同的表中。关系数据库管理员可能会建议不要创建 100 个包含相同行数据的相同表。 从数量角度将语句与关系数据库表进行比较。 在良好的设计中，语句的数量是有限的。运行时并没有专门针对大量语句进行设计。 同样，一个包含 100000 个表的关系数据库模式设计也会受到严重质疑。 关于多少个语句适合内存，这取决于具体的语句，没有通用的指导原则。</p>
				<p>EPL 允许你以重用状态和处理的方式设计 EPL。 例如，你的 EPL 设计可以使用命名窗口，而不是分配 100 个独立的时间窗口。 由于命名窗口是共享的，运行时只需要跟踪一个时间窗口，而不是 100 个。 并且你的 EPL 设计可以使用 EPL 表在中央位置维护一次聚合，这样每个符号的计数只需要跟踪一次，而不是 100 次。</p>
				<p>上下文声明是一种有效的方法，可以提取语句之间的共性（多个语句之间相似的部分），并将其提取到上下文声明中。 与其创建 X 个相似的语句，不如声明一个上下文，并将一个语句附加到该上下文，从而创建 X 个上下文分区。这样可以避免编译和/或部署 X 个相同的语句。 使用上下文时，编译器只需要分析上下文声明和语句。 你的应用程序可以发送启动和停止事件来控制存在哪些上下文分区，以及每个上下文分区分析哪些事件。 使用上下文分区管理 API 来浏览或终止上下文分区。</p>
				<p>
					例如，假设你需要创建 100000 个相似的语句，这些语句都过滤 <code class="literal">GeoEvent</code> 事件：
				</p>
				<pre class="">create schema GeoEvent(id string, value int, marker string)</pre>
				<pre class="">@name('statment-1) select * from GeoEvent(id = '0001', value between 10 and 20, marker in ('a', 'b'))</pre>
				<pre class="">@name('statment-N) select * from GeoEvent(id = '0002', value between 20 and 30, marker in ('c', 'd'))</pre>
				<p>如果你的应用程序编译并部署了 100000 个如上所述的语句，编译器必须分别分析和查询规划每个语句，运行时必须将每组过滤条件输入到共享的过滤索引树中。请记住，即使对于 100000 个语句，运行时也可以非常快速地处理传入事件，具有低延迟和高吞吐量。然而，编译和部署 100000 个单独的语句确实会消耗 CPU 时间。</p>
				<p>
					在这个例子中，这些语句具有相似的过滤器：<code class="literal">id = </code><span class="emphasis"><em>an_id</em></span>、<code class="literal">value between </code><span class="emphasis"><em>start_range</em></span><code class="literal"> and </code><span class="emphasis"><em>end_range</em></span> 和 <code class="literal">marker in (</code><span class="emphasis"><em>markers</em></span><code class="literal">)</code>。 你可以说这些语句是相似的，看起来像：
				</p>
				<pre class="">select * from GeoEvent(id=an_id, value between start_range and end_range, marker in (markers))</pre>
				<p>
					<span class="emphasis"><em>an_id</em></span>、<span class="emphasis"><em>start_range</em></span>、<span class="emphasis"><em>end_range</em></span> 和 <span class="emphasis"><em>markers</em></span> 是过滤语句实例的基本参数。 语句的实例就是上下文分区。声明一个上下文来重构和改变我们的设计，使公共过滤器集中在一处。 这种方法只需要两个语句：上下文声明和带有过滤器的语句。 你可以声明两种事件类型：一种用于分配新的上下文分区，一种用于终止上下文分区。
				</p>
				<p>首先创建一种事件类型，用于控制过滤语句的哪些实例（上下文分区）处于活动状态：</p>
				<pre class="">create schema InitEvent(id string, startRange int, endRange int, markers string[])</pre>
				<p>接下来，创建一种事件类型，用于控制上下文分区何时终止：</p>
				<pre class="">create schema TermEvent(id string)</pre>
				<p>
					上下文声明告诉运行时，当一个 <code class="literal">InitEvent</code> 事件到达时，你希望有一个由 <code class="literal">InitEvent</code> 属性参数化的新实例：
				</p>
				<pre class="">create context GeoEventFilterContext
  initiated by InitEvent as initevent
  terminated by by TermEvent(id=initevent.id)</pre>
				<p>定义过滤语句：</p>
				<pre class="">context GeoEventFilterContext select * from GeoEvent(id = context.initevent.id, 
  value between context.initevent.startRange and context.initevent.endRange, 
  marker in (context.initevent.markers))</pre>
				<p>
					你的应用程序现在可以发送 <code class="literal">InitEvent</code> 实例，例如（来自在线 EPL 工具的表示法）：
				</p>
				<pre class="">InitEvent={id='0001', startRange=10, endRange=20, markers={'a', 'b'}}
InitEvent={id='0002', startRange=20, endRange=30, markers={'c', 'd'}}</pre>
				<p>
					当运行时接收到一个 <code class="literal">InitEvent</code> 实例时，它可以简单地获取 <code class="literal">id</code>、<code class="literal">startRange</code>、<code class="literal">endRange</code> 和 <code class="literal">markers</code> 值，并实例化 EPL 过滤语句（即分配一个新的上下文分区），然后开始查找匹配的 <code class="literal">GeoEvent</code> 事件。
				</p>
				<p>
					要停止查找给定的 id，发送一个 <code class="literal">TermEvent</code>，如下所示：
				</p>
				<pre class="">TermEvent={id='0001'}</pre>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-28"></a>24.2.36. 比较单线程和多线程性能</h3>
						</div>
					</div>
				</div>
				<p>
					Java 虚拟机对锁进行了优化，因此，例如，获取读锁的时间在单线程和多线程应用程序之间有很大差异。 我们比较了获取不公平的 <code class="literal">ReentrantReadWriteLock</code> 读锁 1 亿次的代码，且没有写操作。 我们测量到单线程应用程序花费 3 秒，而 2 线程应用程序花费 15 秒。 因此，不能期望从单线程扩展到 2 线程总是能使性能翻倍。多线程协调存在基本成本。
				</p>
				<div class="tip">
					<h2>提示</h2>
					<p>
						要记录锁活动，请按照 <a class="xref" href="configuration.html#configuration-runtime-logging-lockactivity" title="17.6.2.3. 锁活动日志记录">17.6.2.3节，“锁活动日志记录”</a> 中所述启用运行时锁活动日志记录。
					</p>
				</div>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-29"></a>24.2.37. 命名窗口事件的增量聚合与重新计算聚合</h3>
						</div>
					</div>
				</div>
				<p>命名窗口行的聚合是增量计算还是从头重新计算，取决于语句的类型。</p>
				<p>当运行时增量计算聚合值时，意味着它会随着事件进入和离开命名窗口而持续更新聚合值，这意味着运行时会在内部订阅命名窗口更新，并在更新发生时应用这些更新。对于某些应用程序来说，这是理想的行为。</p>
				<p>对于某些应用程序来说，在某些条件发生时（例如，当触发事件到达或时间流逝时）从头重新计算聚合值是有益的。如果要考虑的行数较少，和/或触发事件或时间条件很少触发，重新计算聚合可能成本更低。</p>
				<p>下一段假设已经创建了一个命名窗口，用于按符号和分钟保存一些历史金融数据：</p>
				<pre class="">create window HistoricalWindow#keepall as (symbol string, int minute, double price)</pre>
				<pre class="">insert into HistoricalWindow select symbol, minute, price from HistoricalTick</pre>
				<p>对于简单地从命名窗口进行选择的语句（不包括 on-select），运行时会增量计算聚合值，随着事件进入和离开命名窗口持续更新聚合。</p>
				<p>例如，下面的语句会随着事件进入和离开命名窗口增量更新总价格。如果在创建语句时命名窗口中已经存在事件，总价格会在创建语句时预先计算一次，然后在事件进入和离开命名窗口时增量更新：</p>
				<pre class="">select sum(price) from HistoricalWindow(symbol='GE')</pre>
				<p>对于无关联子查询也是如此。 对于从命名窗口进行子选择的语句，运行时会增量计算聚合值，随着事件进入和离开命名窗口持续更新聚合。 这仅适用于没有 where 子句的无关联子查询。</p>
				<p>例如，下面的语句会随着事件进入和离开命名窗口增量更新总价格。如果在创建语句时命名窗口中已经存在事件，总价格会在创建语句时预先计算一次，然后在事件进入和离开命名窗口时增量更新：</p>
				<pre class="">// 输出 GE 符号的总价格，增量计算
// 每小时的 0、15、30、45 分钟输出。
select (sum(price) from HistoricalWindow(symbol='GE')) 
from pattern [every timer:at(0, 15, 30, 45), *, *, *, *, 0)]</pre>
				<p>
					如果你的应用程序使用 <code class="literal">on-select</code> 或关联子查询，运行时会在每次触发事件触发时从头重新计算聚合值。
				</p>
				<p>例如，下面的语句不会增量计算总价格（请使用上面的普通选择或子选择）。 相反，运行时会根据 where 子句和匹配的行从头计算总价格：</p>
				<pre class="">// 每小时的 0、15、30、45 分钟输出 GE 符号的总价格（从头重新计算）
on pattern [every timer:at(0, 15, 30, 45), *, *, *, *, 0)]
select sum(price) from HistoricalWindow where symbol='GE'</pre>
				<p>针对命名窗口的单向连接也不会增量计算聚合值。</p>
				<p>非单向的连接和外连接会增量计算聚合值。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-30"></a>24.2.38. 内存何时释放</h3>
						</div>
					</div>
				</div>
				<p>只有在发生垃圾回收时，Java 虚拟机（JVM）才会释放内存。根据你的 JVM 设置，垃圾回收可能频繁发生，也可能很少发生，并且可能会考虑全部或仅部分堆内存。</p>
				<p>运行时针对延迟和吞吐量进行了优化。运行时不会强制进行垃圾回收，也不会干扰垃圾回收。 对于对性能敏感的代码区域，运行时会利用线程局部缓冲区，如数组或环形缓冲区，这些缓冲区可以保留少量最近处理的状态。 出于性能原因，运行时不会在处理每个事件后清理此类缓冲区。它会在销毁运行时和取消部署时清理此类缓冲区。 因此，在处理事件后看到少量不会增加的内存被保留是正常的，垃圾收集器可能不会立即释放这些内存。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-31"></a>24.2.39. 测量不匹配事件和监视的吞吐量</h3>
						</div>
					</div>
				</div>
				<p>当一个事件进入且该事件与任何语句都不匹配时，运行时可以丢弃该事件，因为它是一个不匹配的事件。 在测量吞吐量时，我们建议包括不匹配的事件。运行时能够极快地丢弃不匹配事件，这是处理过程中的一个重要方面。</p>
				<p>许多用例是在大海捞针或寻找很少出现的模式。例如，一个寻找安全漏洞的用例可能会分析 1000 万个事件，而在这 1000 万个输入事件中，可能只发现一个由 5 个相关事件组成的情况。我们建议你的基准测试紧密模拟或回放生产数据，并观察输入和输出事件的预期比例。减少输出事件的数量通常会提高性能。</p>
				<p>例如，假设你有 10 个语句：</p>
				<pre class="">select * from pattern[A -&gt; B(id = 1)];
select * from pattern[A -&gt; B(id = 2)];
.....
select * from pattern[A -&gt; B(id = 10)];</pre>
				<p>上述模式在一个 A 事件之后跟着一个 id 在 1 到 10 之间的 B 事件时，每个模式都会匹配一次。</p>
				<p>我们建议通过发送 id 值为 -1 的 B 事件来测量吞吐量，例如，以确定此类事件被丢弃的速度有多快。</p>
			</div>
			<div class="sect2" lang="en-US">
				<div class="titlepage">
					<div>
						<div>
							<h3 class="title"><a id="perf-tips-32"></a>24.2.40. 当事件类型具有大量事件属性（即大事件）时的处理选项</h3>
						</div>
					</div>
				</div>
				<p>我们将具有超过 1000 个事件属性的事件类型视为具有大量属性的事件类型。 以下是处理大事件的一些可用选项（不按优先顺序排列，这只是一个列表）：</p>
				<div class="itemizedlist">
					<ul>
						<li>
							<p>
								可以设计 EPL，以便仅保留对特定用例重要的那些事件属性，这样 Esper 运行时就可以对原始事件进行垃圾回收，仅保留特定用例所需的少数事件属性。 例如，使用 <code class="literal">insert into ReducedEvent select a,b from LargeEvent</code> 和 <code class="literal">select... from ReducedEvent...</code>。
							</p>
						</li>
						<li>
							<p>可以设计 EPL，使得运行时无需保留大事件对象本身。本文档和常见问题页面详细描述了运行时何时保留事件以及何时不保留事件。</p>
						</li>
						<li>
							<p>使用 EsperHA，因为 EsperHA 能够在需要时从内存中删除状态并将其交换回内存。</p>
						</li>
						<li>
							<p>一般来说，JVM 对于像字符串或数组这样的对象需要更多内存。确保事件属性具有最小的内存占用会有所帮助，即一个基本整数比一个字符串使用的内存少。</p>
						</li>
						<li>
							<p>与其在事件上保留所有事件属性，不如将事件属性减少到仅那些重要的属性。应用程序代码可以在需要时从外部源延迟获取其余属性。</p>
						</li>
						<li>
							<p>事件属性可以以二进制编码存储，这也使用更少的内存（使用外部库或编写自己的二进制编码；Esper 不提供二进制编码）。例如，应用程序代码可以提供查询编码数据的“getter”方法。</p>
						</li>
					</ul>
				</div>
			</div>
		</div>
		<div class="sect1" lang="en-US">
			<div class="titlepage">
				<div>
					<div>
						<h2 class="title"><a id="performance-kit"></a>24.3. 使用性能工具包</h2>
					</div>
				</div>
			</div>
		</div>
		<div class="sect2" lang="en-US">
			<div class="titlepage">
				<div>
					<div>
						<h3 class="title"><a id="how-to-kit"></a>24.3.1. 如何使用性能工具包</h3>
					</div>
				</div>
			</div>
			<p>基准测试应用程序本质上是一个使用运行时构建的事件服务器，它通过TCP监听远程客户端。 远程客户端将MarketData（股票代码、价格、交易量）流发送到事件服务器。 事件服务器启动时会有1000条单一类型的语句（除非另有说明），每个股票代码对应一条语句，除非该语句类型不依赖于股票代码。 语句原型会与结果一起提供，其中用“$”代替实际的股票代码值。 事件服务器完全是多线程的，并且可以充分利用32位或64位底层硬件多处理器多核架构的全部性能。</p>
			<p>该工具包在启动时还会打印出事件大小，以及在100 Mbit/s和1 Gbit/s网络上理论上可获得的最大吞吐量。请记住，尽管事件规模较小，但在使用我们的工具包时，100 Mbit/s的网络在大约每秒400,000个事件时就会过载。</p>
			<p>
				结果会发布在我们的Wiki页面 <a class="link" href="http://www.espertech.com/esper" target="">性能Wiki</a> 上。 报告的结果并不代表曾经获得的最佳结果。报告的结果可以帮助您更好地将Esper与其他解决方案在延迟、吞吐量和CPU利用率方面进行比较，同时也可以评估您的目标硬件和JVM。
			</p>
			<p>
				事件服务器、客户端和语句原型在源代码存储库 <code class="literal">esper/trunk/examples/benchmark/</code> 中提供。 有关源代码访问，请参考 <a class="link" href="http://www.espertech.com/esper" target="">http://www.espertech.com/esper</a>。
			</p>
			<p>如果您使用该工具包，应该：</p>
			<div class="orderedlist">
				<ol>
					<li>
						<p>
							选择您想要进行基准测试的语句，将其添加到 <code class="literal">etc/statements.properties</code> 中您自己的键值下，并且在启动事件服务器时使用 <code class="literal">-mode KEY</code>。
						</p>
					</li>
					<li>
						<p>
							准备好您的runServer.sh/runServer.cmd和runClient.sh/runclient.cmd脚本。您需要将所需的jar库放入 <code class="literal">lib/</code> 中，确保在这些脚本中配置的类路径包含 <code class="literal">build</code> 和 <code class="literal">etc</code>。所需的库包括Esper（任何兼容版本，我们从Esper 1.7.0开始进行了测试）及其依赖项。 请注意，<code class="literal">./etc</code> 和 <code class="literal">./build</code> 必须在类路径中。 在这个阶段，您还应该开始设置JVM堆的最小值和最大值。一个不错的起始设置是1GB，如 <code class="literal">-Xms1g -Xmx1g</code>。
						</p>
					</li>
					<li>
						<p>
							编写您想要进行基准测试的语句，假设客户端将发送一个MarketData（String ticker, int volume, double price）流，将其添加到 <code class="literal">etc/statements.properties</code> 中您自己的键值下，并且在启动事件服务器时使用 <code class="literal">-mode KEY</code>。 在语句中使用 <code class="literal">'$'</code> 来创建一个原型。对于每个股票代码，都会注册一条语句，其中所有的 <code class="literal">'$'</code> 都会被替换为实际的股票代码值（例如 <code class="literal">'GOOG'</code>）。
						</p>
					</li>
					<li>
						<p>
							确保客户端和服务器使用相同的 <code class="literal">-Desper.benchmark.symbol=1000</code> 值。 这会设置要使用的股票代码数量（因此，如果您使用语句原型，可能会设置语句的数量），并且控制MarketData事件在网络上的表示方式。 基本上，为了确保可预测性，网络上的所有事件大小都相同。如果您在此处使用1000作为值，事件的股票代码范围将在 <code class="literal">S0AA</code> 和 <code class="literal">S999A</code> 之间（前缀为S，并使用A填充为固定长度的字符串）。交易量和价格属性将是随机的。
						</p>
					</li>
					<li>
						<p>
							默认情况下，基准测试会为语句注册一个订阅者。使用 <code class="literal">-Desper.benchmark.ul</code> 可以改用UpdateListener。请注意，订阅者包含适合 <code class="literal">etc/statements.properties</code> 文件中默认提议语句的update(..)方法，但如果您更改了语句，由于与语句结果的紧密绑定，可能就不适用了。请参考 <a class="xref" href="apiruntime.html#apiruntime-statement-receiveresults" title="表16.2. 接收语句结果的选择">表16.2，“接收语句结果的选择”</a>。
						</p>
					</li>
					<li>
						<p>
							在模拟模式下（不连接客户端）建立性能基线。使用 <code class="literal">-rate 1x5000</code> 选项来模拟一个客户端（一个线程）每秒发送5000个事件。您可以增加模拟客户端线程的数量及其发送速率，以最大限度地提高CPU利用率。 合适的数值应该模拟您将在客户端/服务器基准测试中使用的客户端发送速率，因此应该与您的客户端机器和网络能够发送的速率一致。 在小型硬件上，在这种模拟模式下，拥有很多速率较慢的线程并不能帮助获得高吞吐量。
						</p>
					</li>
					<li>
						<p>
							在客户端/服务器模式下进行性能测试。从runServer脚本或Ant任务中删除 <code class="literal">-rate NxM</code> 选项。 使用 <code class="literal">-help</code> 启动服务器，以显示可能的服务器选项（监听端口、统计信息、扇出选项等）。 在远程机器上，启动一个或多个客户端。使用 <code class="literal">-help</code> 显示可能的客户端选项（远程端口、主机、发送速率）。客户端将输出它实际发送到服务器的事件数量。 如果服务器过载（或者如果您在服务器上启用了 <code class="literal">-queue</code> 选项），客户端可能无法达到其目标速率。
						</p>
						<p>
							通常，使用服务器端的 <code class="literal">-queue -1</code> 选项可以获得更好的性能，这样每个客户端连接都由单个线程管道处理。如果您将其更改为0或更大的值，将会有中间结构以异步方式传递事件流。这将增加上下文切换，不过，如果您使用许多客户端，或者使用 <code class="literal">-sleep xxx</code>（xxx以毫秒为单位）来模拟监听器延迟，您可能会获得更好的性能。
						</p>
						<p>
							最重要的服务器端选项是 <code class="literal">-stat xxx</code>（xxx以秒为单位），用于打印在过去xxx秒内聚合的吞吐量和延迟统计信息（并且每次都会重置）。 它将产生内部延迟（以纳秒为单位）以及端到端延迟（以毫秒为单位，包括网络时间）。 如果您正在测量端到端延迟，您应该确保您的服务器和客户端机器的时间相同，例如使用精度足够高的ntpd。 统计信息格式如下：
						</p> <pre class="">---Stats - runtime (unit: ns)
  Avg: 2528 #4101107
        0 &lt;    5000:  97.01%  97.01% #3978672
     5000 &lt;   10000:   2.60%  99.62% #106669
    10000 &lt;   15000:   0.35%  99.97% #14337
    15000 &lt;   20000:   0.02%  99.99% #971
    20000 &lt;   25000:   0.00%  99.99% #177
    25000 &lt;   50000:   0.00% 100.00% #89
    50000 &lt;  100000:   0.00% 100.00% #41
   100000 &lt;  500000:   0.00% 100.00% #120
   500000 &lt; 1000000:   0.00% 100.00% #2
  1000000 &lt; 2500000:   0.00% 100.00% #7
  2500000 &lt; 5000000:   0.00% 100.00% #5
  5000000 &lt;    more:   0.00% 100.00% #18
---Stats - endToEnd (unit: ms)
  Avg: -2704829444341073400 #4101609
        0 &lt;       1:  75.01%  75.01% #3076609
        1 &lt;       5:   0.00%  75.01% #0
        5 &lt;      10:   0.00%  75.01% #0
       10 &lt;      50:   0.00%  75.01% #0
       50 &lt;     100:   0.00%  75.01% #0
      100 &lt;     250:   0.00%  75.01% #0
      250 &lt;     500:   0.00%  75.01% #0
      500 &lt;    1000:   0.00%  75.01% #0
     1000 &lt;    more:  24.99% 100.00% #1025000
Throughput 412503 (active 0 pending 0 cnx 4)</pre>
						<p>这段内容的解读如下：</p> <pre class="">"吞吐量为每秒412,503个事件，有4个客户端连接。未使用 -queue 选项，因此在打印统计信息时没有事件处于等待状态。
对于4,101,107个事件，延迟平均值为2528纳秒（即2.5微秒）（这意味着这里是10秒的统计数据）。对于106,669个事件，延迟小于10微秒，占比99.62%。在该时间段内，2.60%的事件延迟在5微秒到10微秒之间。"

"在这种情况下，端到端延迟…… 可能是由于客户端时钟差异，最终得到的端到端统计数据不可用。"</pre>
						<p>考虑关于端到端延迟的第二段输出内容：</p> <pre class="">---Stats - endToEnd (unit: ms)
  Avg: 15 #863396
        0 &lt;       1:   0.75%   0.75% #6434
        1 &lt;       5:   0.99%   1.74% #8552
        5 &lt;      10:   2.12%   3.85% #18269
       10 &lt;      50:  91.27%  95.13% #788062
       50 &lt;     100:   0.10%  95.32% #827
      100 &lt;     250:   4.36%  99.58% #37634
      250 &lt;     500:   0.42% 100.00% #3618
      500 &lt;    1000:   0.00% 100.00% #0
     1000 &lt;    more:   0.00% 100.00% #0</pre>
						<p>这段内容的解读如下：</p> <pre class="">"对于这份统计报告所考虑的863,396个事件，端到端延迟平均值为15毫秒。95.13%（即788,062个事件）的端到端处理时间在50毫秒以下，91.27%的事件处理时间在10毫秒到50毫秒之间。"</pre>
					</li>
				</ol>
			</div>
		</div>
	</div>
	<ul class="docnav">
		<li class="previous"><a accesskey="p" href="examples.html"><strong>上一页</strong>第23章. 示例、教程、案例研究</a></li>
		<li class="up"><a accesskey="u" href="#"><strong>返回顶部</strong></a></li>
		<li class="home"><a accesskey="h" href="index.html"><strong>首页</strong></a></li>
		<li class="next"><a accesskey="n" href="references.html"><strong>下一页</strong>第25章. 参考文献</a></li>
	</ul>
</body>

</html>